From f0a42f703527bdf1fe13bec8be79ae8b77255198 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 1 Feb 2016 09:59:37 -0800 Subject: [PATCH] Fix problems with graceful-fs bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public This fixes the problem that graceful-fs exposes an unpatched `close()` method, that will never trigger retries. This behavior deadlocks the packager when trying to use graceful-fs’s `open()`/`read()`/`close()` methods. See: isaacs/node-graceful-fs#56 Reviewed By: cpojer Differential Revision: D2885512 fb-gh-sync-id: 71112d2488929bf1775fe9f8566fa03fd66b6bea --- .../src/DependencyResolver/fastfs.js | 96 ++++++++++++------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index f33b9927..325604ff 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -14,7 +14,10 @@ const {EventEmitter} = require('events'); const fs = require('graceful-fs'); const path = require('path'); -const open = Promise.denodeify(fs.open); +// workaround for https://github.com/isaacs/node-graceful-fs/issues/56 +// fs.close is patched, whereas graceful-fs.close is not. +const fsClose = require('fs').close; + const readFile = Promise.denodeify(fs.readFile); const stat = Promise.denodeify(fs.stat); @@ -251,41 +254,12 @@ class File { } readWhile(predicate) { - const CHUNK_SIZE = 512; - let result = ''; - - return open(this.path, 'r').then(fd => { - /* global Buffer: true */ - const buffer = new Buffer(CHUNK_SIZE); - const p = new Promise((resolve, reject) => { - let counter = 0; - const callback = (error, bytesRead) => { - if (error) { - reject(); - return; - } - - const chunk = buffer.toString('utf8', 0, bytesRead); - result += chunk; - if (bytesRead > 0 && predicate(chunk, counter++, result)) { - readChunk(fd, buffer, callback); - } else { - if (bytesRead === 0 && !this._read) { // reached EOF - this._read = Promise.resolve(result); - } - resolve(result); - } - }; - readChunk(fd, buffer, callback); - }); - - p.catch(() => fs.close(fd)); - return p; + return readWhile(this.path, predicate).then(({result, completed}) => { + if (completed && !this._read) { + this._read = Promise.resolve(result); + } + return result; }); - - function readChunk(fd, buffer, callback) { - fs.read(fd, buffer, 0, CHUNK_SIZE, null, callback); - } } stat() { @@ -365,6 +339,58 @@ class File { } } +function readWhile(filePath, predicate) { + return new Promise((resolve, reject) => { + fs.open(filePath, 'r', (openError, fd) => { + if (openError) { + reject(openError); + return; + } + + read( + fd, + /*global Buffer: true*/ + new Buffer(512), + makeReadCallback(fd, predicate, (readError, result, completed) => { + if (readError) { + reject(readError); + } else { + resolve({result, completed}); + } + }) + ); + }); + }); +} + +function read(fd, buffer, callback) { + fs.read(fd, buffer, 0, buffer.length, -1, callback); +} + +function close(fd, error, result, complete, callback) { + fsClose(fd, closeError => callback(error || closeError, result, complete)); +} + +function makeReadCallback(fd, predicate, callback) { + let result = ''; + let index = 0; + return function readCallback(error, bytesRead, buffer) { + if (error) { + close(fd, error, undefined, false, callback); + return; + } + + const completed = bytesRead === 0; + const chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead); + result += chunk; + if (completed || !predicate(chunk, index++, result)) { + close(fd, null, result, completed, callback); + } else { + read(fd, buffer, readCallback); + } + }; +} + function isDescendant(root, child) { return path.relative(root, child).indexOf('..') !== 0; }