Skip to content

Commit

Permalink
fs: refactor close to use destroy
Browse files Browse the repository at this point in the history
Refactor close() to use destroy() and not vice versa in ReadStream.
Avoid races between WriteStream.close and WriteStream.write, by aliasing
close to end().

Fixes: #2006
  • Loading branch information
mcollina committed Sep 20, 2017
1 parent 8589c70 commit 2985aca
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 29 deletions.
60 changes: 34 additions & 26 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2090,44 +2090,38 @@ ReadStream.prototype._read = function(n) {
}
};


ReadStream.prototype._destroy = function(err, cb) {
this.close(function(err2) {
cb(err || err2);
});
};


ReadStream.prototype.close = function(cb) {
if (cb)
this.once('close', cb);

if (this.closed || typeof this.fd !== 'number') {
if (typeof this.fd !== 'number') {
this.once('open', closeOnOpen);
this.once('open', closeFsStream.bind(null, this, cb, err));
return;
}
return process.nextTick(() => this.emit('close'));

return process.nextTick(() => {
cb(err);
this.emit('close');
});
}

this.closed = true;

fs.close(this.fd, (er) => {
if (er)
this.emit('error', er);
else
this.emit('close');
});

closeFsStream(this, cb);
this.fd = null;
};

// needed because as it will be called with arguments
// that does not match this.close() signature
function closeOnOpen(fd) {
this.close();
function closeFsStream(stream, cb, err) {
fs.close(stream.fd, (er) => {
er = er || err;
cb(er);
if (!er)
stream.emit('close');
});
}

ReadStream.prototype.close = function(cb) {
this.destroy(null, cb);
};

fs.createWriteStream = function(path, options) {
return new WriteStream(path, options);
};
Expand Down Expand Up @@ -2179,7 +2173,7 @@ function WriteStream(path, options) {
// dispose on finish.
this.once('finish', function() {
if (this.autoClose) {
this.close();
this.destroy();
}
});
}
Expand Down Expand Up @@ -2276,7 +2270,21 @@ WriteStream.prototype._writev = function(data, cb) {


WriteStream.prototype._destroy = ReadStream.prototype._destroy;
WriteStream.prototype.close = ReadStream.prototype.close;
WriteStream.prototype.close = function(cb) {
if (this._writableState.ending) {
this.on('close', cb);
return;
}

if (this._writableState.ended) {
process.nextTick(cb);
return;
}

// we use end() instead of destroy() because of
// https://github.com/nodejs/node/issues/2006
this.end(cb);
};

// There is no shutdown() for files.
WriteStream.prototype.destroySoon = WriteStream.prototype.end;
Expand Down
16 changes: 13 additions & 3 deletions test/parallel/test-fs-read-stream-double-close.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
const common = require('../common');
const fs = require('fs');

const s = fs.createReadStream(__filename);
{
const s = fs.createReadStream(__filename);

s.close(common.mustCall());
s.close(common.mustCall());
s.close(common.mustCall());
s.close(common.mustCall());
}

{
const s = fs.createReadStream(__filename);

// this is a private API, but it is worth esting. close calls this
s.destroy(null, common.mustCall());
s.destroy(null, common.mustCall());
}

0 comments on commit 2985aca

Please sign in to comment.