diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 4700120b0dd4..43169834ff5a 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -2721,6 +2721,15 @@ class Http2Stream extends Duplex { }); } } + + // A write in flight may never get its native completion callback once the + // stream tears down; settle it so the Writable can clean up. + if (state.writeCb !== null) { + const writeCb = state.writeCb; + state.writeCb = null; + state.writePending = 0; + writeCb(err); + } callback(err); } // The Http2Stream can be destroyed if it has closed and if the readable diff --git a/test/parallel/test-http2-close-while-writing.js b/test/parallel/test-http2-close-while-writing.js index 17931005dc6c..c78386c85f23 100644 --- a/test/parallel/test-http2-close-while-writing.js +++ b/test/parallel/test-http2-close-while-writing.js @@ -29,11 +29,21 @@ server.on('session', common.mustCall(function(session) { stream.on('error', common.mustCall((err) => { assert.strictEqual(err.code, 'ERR_HTTP2_STREAM_ABORTED'); })); - stream.resume(); + + // Every write dispatched before close must have its callback invoked. + let writes = 0; + let writeCallbacks = 0; stream.on('data', function() { - this.write(Buffer.alloc(1)); + writes++; + this.write(Buffer.alloc(1), () => { + writeCallbacks++; + }); process.nextTick(() => client_stream.destroy()); }); + stream.on('close', common.mustCall(() => { + assert.strictEqual(writeCallbacks, writes); + })); + stream.resume(); })); }));