-
Notifications
You must be signed in to change notification settings - Fork 30.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
stream: fix end-of-stream for HTTP/2
HTTP/2 streams call `.end()` on themselves from their `.destroy()` method, which might be queued (e.g. due to network congestion) and not processed before the stream itself is destroyed. In that case, the `_writableState.ended` property could be set before the stream emits its `'close'` event, and never actually emits the `'finished'` event, confusing the end-of-stream implementation so that it wouldn’t call its callback. This can be fixed by watching for the end events themselves using the existing `'finish'` and `'end'` listeners rather than relying on the `.ended` properties of the `_...State` objects. These properties still need to be checked to know whether stream closure was premature – My understanding is that ideally, streams should not emit `'close'` before `'end'` and/or `'finished'`, so this might be another bug, but changing this would require modifying tests and almost certainly be a breaking change. Fixes: #24456 PR-URL: #24926 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
- Loading branch information
1 parent
bdf21c1
commit bb8a65d
Showing
3 changed files
with
53 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
test/parallel/test-stream-pipeline-queued-end-in-destroy.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const { Readable, Duplex, pipeline } = require('stream'); | ||
|
||
// Test that the callback for pipeline() is called even when the ._destroy() | ||
// method of the stream places an .end() request to itself that does not | ||
// get processed before the destruction of the stream (i.e. the 'close' event). | ||
// Refs: https://github.com/nodejs/node/issues/24456 | ||
|
||
const readable = new Readable({ | ||
read: common.mustCall(() => {}) | ||
}); | ||
|
||
const duplex = new Duplex({ | ||
write(chunk, enc, cb) { | ||
// Simulate messages queueing up. | ||
}, | ||
read() {}, | ||
destroy(err, cb) { | ||
// Call end() from inside the destroy() method, like HTTP/2 streams | ||
// do at the time of writing. | ||
this.end(); | ||
cb(err); | ||
} | ||
}); | ||
|
||
duplex.on('finished', common.mustNotCall()); | ||
|
||
pipeline(readable, duplex, common.mustCall((err) => { | ||
assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); | ||
})); | ||
|
||
// Write one chunk of data, and destroy the stream later. | ||
// That should trigger the pipeline destruction. | ||
readable.push('foo'); | ||
setImmediate(() => { | ||
readable.destroy(); | ||
}); |