mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
When a stream is destroyed while a write is still in flight, nghttp2 may have handed the data to the socket and never report the write completion once the stream or session tears down. That leaves the write callback unresolved, the Writable stuck, and the event loop never drains, which times out test-http2-close-while-writing on macOS. Settle the in-flight write in Http2Stream._destroy by invoking its callback with the destroy error and resetting writePending. A later native completion callback is then a no-op because writeCb is null and writePending is 0, so the settle is idempotent. Passing the error rather than null also drains any buffered writes. Fixes: https://github.com/nodejs/node/issues/58252 Signed-off-by: Matteo Collina <hello@matteocollina.com> Assisted-by: pi-coding-agent PR-URL: https://github.com/nodejs/node/pull/66016 Fixes: https://github.com/nodejs/node/issues/58252 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
63 lines
1.8 KiB
JavaScript
63 lines
1.8 KiB
JavaScript
'use strict';
|
|
// https://github.com/nodejs/node/issues/33156
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const fixtures = require('../common/fixtures');
|
|
|
|
if (!common.hasCrypto) {
|
|
common.skip('missing crypto');
|
|
}
|
|
|
|
const http2 = require('http2');
|
|
|
|
const key = fixtures.readKey('agent8-key.pem', 'binary');
|
|
const cert = fixtures.readKey('agent8-cert.pem', 'binary');
|
|
const ca = fixtures.readKey('fake-startcom-root-cert.pem', 'binary');
|
|
|
|
const server = http2.createSecureServer({
|
|
key,
|
|
cert,
|
|
maxSessionMemory: 1000
|
|
});
|
|
|
|
let client_stream;
|
|
|
|
server.on('session', common.mustCall(function(session) {
|
|
session.on('stream', common.mustCall(function(stream) {
|
|
// Client destroys mid-stream without END_STREAM (clean RST code).
|
|
// Peer reset before END_STREAM surfaces as ERR_HTTP2_STREAM_ABORTED.
|
|
stream.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err.code, 'ERR_HTTP2_STREAM_ABORTED');
|
|
}));
|
|
|
|
// Every write dispatched before close must have its callback invoked.
|
|
let writes = 0;
|
|
let writeCallbacks = 0;
|
|
stream.on('data', function() {
|
|
writes++;
|
|
this.write(Buffer.alloc(1), () => {
|
|
writeCallbacks++;
|
|
});
|
|
process.nextTick(() => client_stream.destroy());
|
|
});
|
|
stream.on('close', common.mustCall(() => {
|
|
assert.strictEqual(writeCallbacks, writes);
|
|
}));
|
|
stream.resume();
|
|
}));
|
|
}));
|
|
|
|
server.listen(0, common.mustCall(() => {
|
|
const client = http2.connect(`https://localhost:${server.address().port}`, {
|
|
ca,
|
|
maxSessionMemory: 1000
|
|
});
|
|
client_stream = client.request({ ':method': 'POST' });
|
|
client_stream.on('close', common.mustCall(() => {
|
|
client.close();
|
|
server.close();
|
|
}));
|
|
client_stream.resume();
|
|
client_stream.write(Buffer.alloc(64 * 1024));
|
|
}));
|