Fix problems with graceful-fs bug

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
This commit is contained in:
David Aurelio 2016-02-01 09:59:37 -08:00 committed by facebook-github-bot-7
parent 5d21d04e5a
commit f0a42f7035
1 changed files with 61 additions and 35 deletions

View File

@ -14,7 +14,10 @@ const {EventEmitter} = require('events');
const fs = require('graceful-fs'); const fs = require('graceful-fs');
const path = require('path'); 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 readFile = Promise.denodeify(fs.readFile);
const stat = Promise.denodeify(fs.stat); const stat = Promise.denodeify(fs.stat);
@ -251,41 +254,12 @@ class File {
} }
readWhile(predicate) { readWhile(predicate) {
const CHUNK_SIZE = 512; return readWhile(this.path, predicate).then(({result, completed}) => {
let result = ''; if (completed && !this._read) {
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); this._read = Promise.resolve(result);
} }
resolve(result); return result;
}
};
readChunk(fd, buffer, callback);
}); });
p.catch(() => fs.close(fd));
return p;
});
function readChunk(fd, buffer, callback) {
fs.read(fd, buffer, 0, CHUNK_SIZE, null, callback);
}
} }
stat() { 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) { function isDescendant(root, child) {
return path.relative(root, child).indexOf('..') !== 0; return path.relative(root, child).indexOf('..') !== 0;
} }