Skip to content

Commit f148556

Browse files
mcollinaMylesBorins
authored andcommitted
fs: guarantee order of callbacks in ws.close
Refactor WriteStream.prototype.close and WriteStream.prototype._destroy to always call the callback passed to close in order. Protects from calling .close() without a callback. Fixes: #17951 See: #15407 PR-URL: #18002 Fixes: #17951 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 0e116a0 commit f148556

4 files changed

+77
-27
lines changed

lib/fs.js

+19-19
Original file line numberDiff line numberDiff line change
@@ -2002,6 +2002,7 @@ function ReadStream(path, options) {
20022002
this.autoClose = options.autoClose === undefined ? true : options.autoClose;
20032003
this.pos = undefined;
20042004
this.bytesRead = 0;
2005+
this.closed = false;
20052006

20062007
if (this.start !== undefined) {
20072008
if (typeof this.start !== 'number') {
@@ -2115,20 +2116,12 @@ ReadStream.prototype._read = function(n) {
21152116
};
21162117

21172118
ReadStream.prototype._destroy = function(err, cb) {
2118-
if (this.closed || typeof this.fd !== 'number') {
2119-
if (typeof this.fd !== 'number') {
2120-
this.once('open', closeFsStream.bind(null, this, cb, err));
2121-
return;
2122-
}
2123-
2124-
return process.nextTick(() => {
2125-
cb(err);
2126-
this.emit('close');
2127-
});
2119+
const isOpen = typeof this.fd !== 'number';
2120+
if (isOpen) {
2121+
this.once('open', closeFsStream.bind(null, this, cb, err));
2122+
return;
21282123
}
21292124

2130-
this.closed = true;
2131-
21322125
closeFsStream(this, cb);
21332126
this.fd = null;
21342127
};
@@ -2137,6 +2130,7 @@ function closeFsStream(stream, cb, err) {
21372130
fs.close(stream.fd, (er) => {
21382131
er = er || err;
21392132
cb(er);
2133+
stream.closed = true;
21402134
if (!er)
21412135
stream.emit('close');
21422136
});
@@ -2169,6 +2163,7 @@ function WriteStream(path, options) {
21692163
this.autoClose = options.autoClose === undefined ? true : !!options.autoClose;
21702164
this.pos = undefined;
21712165
this.bytesWritten = 0;
2166+
this.closed = false;
21722167

21732168
if (this.start !== undefined) {
21742169
if (typeof this.start !== 'number') {
@@ -2294,19 +2289,24 @@ WriteStream.prototype._writev = function(data, cb) {
22942289

22952290
WriteStream.prototype._destroy = ReadStream.prototype._destroy;
22962291
WriteStream.prototype.close = function(cb) {
2297-
if (this._writableState.ending) {
2298-
this.on('close', cb);
2299-
return;
2292+
if (cb) {
2293+
if (this.closed) {
2294+
process.nextTick(cb);
2295+
return;
2296+
} else {
2297+
this.on('close', cb);
2298+
}
23002299
}
23012300

2302-
if (this._writableState.ended) {
2303-
process.nextTick(cb);
2304-
return;
2301+
// If we are not autoClosing, we should call
2302+
// destroy on 'finish'.
2303+
if (!this.autoClose) {
2304+
this.on('finish', this.destroy.bind(this));
23052305
}
23062306

23072307
// we use end() instead of destroy() because of
23082308
// https://github.com/nodejs/node/issues/2006
2309-
this.end(cb);
2309+
this.end();
23102310
};
23112311

23122312
// There is no shutdown() for files.

test/parallel/test-fs-write-stream-autoclose-option.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ let stream = fs.createWriteStream(file, { flags: 'w+', autoClose: false });
1010
stream.write('Test1');
1111
stream.end();
1212
stream.on('finish', common.mustCall(function() {
13+
stream.on('close', common.mustNotCall());
1314
process.nextTick(common.mustCall(function() {
14-
assert.strictEqual(stream.closed, undefined);
15+
assert.strictEqual(stream.closed, false);
1516
assert.notStrictEqual(stream.fd, null);
1617
next();
1718
}));
@@ -23,9 +24,12 @@ function next() {
2324
stream.write('Test2');
2425
stream.end();
2526
stream.on('finish', common.mustCall(function() {
26-
assert.strictEqual(stream.closed, true);
27+
assert.strictEqual(stream.closed, false);
2728
assert.strictEqual(stream.fd, null);
28-
process.nextTick(common.mustCall(next2));
29+
stream.on('close', common.mustCall(function() {
30+
assert.strictEqual(stream.closed, true);
31+
process.nextTick(next2);
32+
}));
2933
}));
3034
}
3135

@@ -44,9 +48,10 @@ function next3() {
4448
stream.write('Test3');
4549
stream.end();
4650
stream.on('finish', common.mustCall(function() {
47-
process.nextTick(common.mustCall(function() {
51+
assert.strictEqual(stream.closed, false);
52+
assert.strictEqual(stream.fd, null);
53+
stream.on('close', common.mustCall(function() {
4854
assert.strictEqual(stream.closed, true);
49-
assert.strictEqual(stream.fd, null);
5055
}));
5156
}));
5257
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
common.refreshTmpDir();
8+
9+
const s = fs.createWriteStream(path.join(common.tmpDir, 'nocallback'));
10+
11+
s.end('hello world');
12+
s.close();
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,45 @@
11
'use strict';
22

33
const common = require('../common');
4+
const assert = require('assert');
45
const fs = require('fs');
56
const path = require('path');
67

78
common.refreshTmpDir();
89

9-
const s = fs.createWriteStream(path.join(common.tmpDir, 'rw'));
10+
{
11+
const s = fs.createWriteStream(path.join(common.tmpDir, 'rw'));
1012

11-
s.close(common.mustCall());
12-
s.close(common.mustCall());
13+
s.close(common.mustCall());
14+
s.close(common.mustCall());
15+
}
16+
17+
{
18+
const s = fs.createWriteStream(path.join(common.tmpDir, 'rw2'));
19+
20+
let emits = 0;
21+
s.on('close', () => {
22+
emits++;
23+
});
24+
25+
s.close(common.mustCall(() => {
26+
assert.strictEqual(emits, 1);
27+
s.close(common.mustCall(() => {
28+
assert.strictEqual(emits, 1);
29+
}));
30+
process.nextTick(() => {
31+
s.close(common.mustCall(() => {
32+
assert.strictEqual(emits, 1);
33+
}));
34+
});
35+
}));
36+
}
37+
38+
{
39+
const s = fs.createWriteStream(path.join(common.tmpDir, 'rw'), {
40+
autoClose: false
41+
});
42+
43+
s.close(common.mustCall());
44+
s.close(common.mustCall());
45+
}

0 commit comments

Comments
 (0)