Skip to content

Commit 6213fce

Browse files
committed
stream: save error in state
Useful for future PR's to resolve situations where e.g. finished() is invoked on an already errored streams. PR-URL: #34103 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Luigi Pinca <[email protected]>
1 parent 60a217b commit 6213fce

File tree

6 files changed

+81
-34
lines changed

6 files changed

+81
-34
lines changed

lib/_stream_readable.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ function ReadableState(options, stream, isDuplex) {
155155
// _read calls, 'data' or 'readable' events should occur. This is needed
156156
// since when autoDestroy is disabled we need a way to tell whether the
157157
// stream has failed.
158-
this.errored = false;
158+
this.errored = null;
159159

160160
// Indicates whether the stream has finished destroying.
161161
this.closed = false;

lib/_stream_writable.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ function WritableState(options, stream, isDuplex) {
179179
// Indicates whether the stream has errored. When true all write() calls
180180
// should return false. This is needed since when autoDestroy
181181
// is disabled we need a way to tell whether the stream has failed.
182-
this.errored = false;
182+
this.errored = null;
183183

184184
// Indicates whether the stream has finished destroying.
185185
this.closed = false;
@@ -436,12 +436,17 @@ function onwrite(stream, er) {
436436
state.writelen = 0;
437437

438438
if (er) {
439-
state.errored = true;
439+
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
440+
er.stack;
441+
442+
if (!state.errored) {
443+
state.errored = er;
444+
}
440445

441446
// In case of duplex streams we need to notify the readable side of the
442447
// error.
443-
if (stream._readableState) {
444-
stream._readableState.errored = true;
448+
if (stream._readableState && !stream._readableState.errored) {
449+
stream._readableState.errored = er;
445450
}
446451

447452
if (sync) {

lib/internal/streams/destroy.js

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ function destroy(err, cb) {
2525
}
2626

2727
if (err) {
28-
if (w) {
29-
w.errored = true;
28+
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
29+
err.stack;
30+
31+
if (w && !w.errored) {
32+
w.errored = err;
3033
}
31-
if (r) {
32-
r.errored = true;
34+
if (r && !r.errored) {
35+
r.errored = err;
3336
}
3437
}
3538

@@ -61,11 +64,14 @@ function _destroy(self, err, cb) {
6164
const w = self._writableState;
6265

6366
if (err) {
64-
if (w) {
65-
w.errored = true;
67+
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
68+
err.stack;
69+
70+
if (w && !w.errored) {
71+
w.errored = err;
6672
}
67-
if (r) {
68-
r.errored = true;
73+
if (r && !r.errored) {
74+
r.errored = err;
6975
}
7076
}
7177

@@ -136,7 +142,7 @@ function undestroy() {
136142
r.closed = false;
137143
r.closeEmitted = false;
138144
r.destroyed = false;
139-
r.errored = false;
145+
r.errored = null;
140146
r.errorEmitted = false;
141147
r.reading = false;
142148
r.ended = false;
@@ -148,7 +154,7 @@ function undestroy() {
148154
w.destroyed = false;
149155
w.closed = false;
150156
w.closeEmitted = false;
151-
w.errored = false;
157+
w.errored = null;
152158
w.errorEmitted = false;
153159
w.ended = false;
154160
w.ending = false;
@@ -175,11 +181,14 @@ function errorOrDestroy(stream, err, sync) {
175181
if ((r && r.autoDestroy) || (w && w.autoDestroy))
176182
stream.destroy(err);
177183
else if (err) {
178-
if (w) {
179-
w.errored = true;
184+
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
185+
err.stack;
186+
187+
if (w && !w.errored) {
188+
w.errored = err;
180189
}
181-
if (r) {
182-
r.errored = true;
190+
if (r && !r.errored) {
191+
r.errored = err;
183192
}
184193
if (sync) {
185194
process.nextTick(emitErrorNT, stream, err);

test/parallel/test-stream-readable-destroy.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@ const assert = require('assert');
134134
read.on('error', common.mustCall((err) => {
135135
assert.strictEqual(ticked, true);
136136
assert.strictEqual(read._readableState.errorEmitted, true);
137-
assert.strictEqual(read._readableState.errored, true);
137+
assert.strictEqual(read._readableState.errored, expected);
138138
assert.strictEqual(err, expected);
139139
}));
140140

141141
read.destroy();
142142
assert.strictEqual(read._readableState.errorEmitted, false);
143-
assert.strictEqual(read._readableState.errored, true);
143+
assert.strictEqual(read._readableState.errored, expected);
144144
assert.strictEqual(read.destroyed, true);
145145
ticked = true;
146146
}
@@ -190,15 +190,15 @@ const assert = require('assert');
190190
assert.strictEqual(err, expected);
191191
}));
192192

193-
assert.strictEqual(read._readableState.errored, false);
193+
assert.strictEqual(read._readableState.errored, null);
194194
assert.strictEqual(read._readableState.errorEmitted, false);
195195

196196
read.destroy(expected, common.mustCall(function(err) {
197-
assert.strictEqual(read._readableState.errored, true);
197+
assert.strictEqual(read._readableState.errored, expected);
198198
assert.strictEqual(err, expected);
199199
}));
200200
assert.strictEqual(read._readableState.errorEmitted, false);
201-
assert.strictEqual(read._readableState.errored, true);
201+
assert.strictEqual(read._readableState.errored, expected);
202202
ticked = true;
203203
}
204204

@@ -223,14 +223,14 @@ const assert = require('assert');
223223

224224
readable.destroy();
225225
assert.strictEqual(readable.destroyed, true);
226-
assert.strictEqual(readable._readableState.errored, false);
226+
assert.strictEqual(readable._readableState.errored, null);
227227
assert.strictEqual(readable._readableState.errorEmitted, false);
228228

229229
// Test case where `readable.destroy()` is called again with an error before
230230
// the `_destroy()` callback is called.
231231
readable.destroy(new Error('kaboom 2'));
232232
assert.strictEqual(readable._readableState.errorEmitted, false);
233-
assert.strictEqual(readable._readableState.errored, false);
233+
assert.strictEqual(readable._readableState.errored, null);
234234

235235
ticked = true;
236236
}
@@ -253,3 +253,18 @@ const assert = require('assert');
253253
assert.strictEqual(read.destroyed, true);
254254
read.read();
255255
}
256+
257+
{
258+
const read = new Readable({
259+
autoDestroy: false,
260+
read() {
261+
this.push(null);
262+
this.push('asd');
263+
}
264+
});
265+
266+
read.on('error', common.mustCall(() => {
267+
assert(read._readableState.errored);
268+
}));
269+
read.resume();
270+
}

test/parallel/test-stream-writable-destroy.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,10 @@ const assert = require('assert');
167167
assert.strictEqual(write._writableState.errorEmitted, true);
168168
}));
169169

170-
write.destroy(new Error('kaboom 1'));
170+
const expected = new Error('kaboom 1');
171+
write.destroy(expected);
171172
write.destroy(new Error('kaboom 2'));
172-
assert.strictEqual(write._writableState.errored, true);
173+
assert.strictEqual(write._writableState.errored, expected);
173174
assert.strictEqual(write._writableState.errorEmitted, false);
174175
assert.strictEqual(write.destroyed, true);
175176
ticked = true;
@@ -200,14 +201,14 @@ const assert = require('assert');
200201

201202
writable.destroy();
202203
assert.strictEqual(writable.destroyed, true);
203-
assert.strictEqual(writable._writableState.errored, false);
204+
assert.strictEqual(writable._writableState.errored, null);
204205
assert.strictEqual(writable._writableState.errorEmitted, false);
205206

206207
// Test case where `writable.destroy()` is called again with an error before
207208
// the `_destroy()` callback is called.
208209
writable.destroy(new Error('kaboom 2'));
209210
assert.strictEqual(writable._writableState.errorEmitted, false);
210-
assert.strictEqual(writable._writableState.errored, false);
211+
assert.strictEqual(writable._writableState.errored, null);
211212

212213
ticked = true;
213214
}
@@ -401,3 +402,18 @@ const assert = require('assert');
401402
}));
402403
write.destroy();
403404
}
405+
406+
{
407+
const write = new Writable({
408+
autoDestroy: false,
409+
write(chunk, enc, cb) {
410+
cb();
411+
cb();
412+
}
413+
});
414+
415+
write.on('error', common.mustCall(() => {
416+
assert(write._writableState.errored);
417+
}));
418+
write.write('asd');
419+
}

test/parallel/test-stream2-readable-wrap-error.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,25 @@ oldStream.pause = () => {};
1010
oldStream.resume = () => {};
1111

1212
{
13+
const err = new Error();
1314
const r = new Readable({ autoDestroy: true })
1415
.wrap(oldStream)
1516
.on('error', common.mustCall(() => {
1617
assert.strictEqual(r._readableState.errorEmitted, true);
17-
assert.strictEqual(r._readableState.errored, true);
18+
assert.strictEqual(r._readableState.errored, err);
1819
assert.strictEqual(r.destroyed, true);
1920
}));
20-
oldStream.emit('error', new Error());
21+
oldStream.emit('error', err);
2122
}
2223

2324
{
25+
const err = new Error();
2426
const r = new Readable({ autoDestroy: false })
2527
.wrap(oldStream)
2628
.on('error', common.mustCall(() => {
2729
assert.strictEqual(r._readableState.errorEmitted, true);
28-
assert.strictEqual(r._readableState.errored, true);
30+
assert.strictEqual(r._readableState.errored, err);
2931
assert.strictEqual(r.destroyed, false);
3032
}));
31-
oldStream.emit('error', new Error());
33+
oldStream.emit('error', err);
3234
}

0 commit comments

Comments
 (0)