From 4ca88ebdb3e49f0a366119802fd9399d26a740bf Mon Sep 17 00:00:00 2001 From: lazerg Date: Sat, 26 Sep 2026 07:27:40 +0500 Subject: [PATCH] http2: submit RST_STREAM before emitting 'aborted' Signed-off-by: lazerg --- lib/internal/http2/core.js | 15 +++++---- .../test-http2-close-aborted-destroy.js | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 test/parallel/test-http2-close-aborted-destroy.js diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 1774808da137..9f14c5f2aeff 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -2153,14 +2153,7 @@ function closeStream(stream, code, rstStreamStatus = kSubmitRstStream) { stream.setTimeout(0); stream.removeAllListeners('timeout'); - // Emit 'aborted' if the user hadn't ended the writable yet. This is - // unusual and doesn't match 'error' but is deprecated & preserved for - // backward compat (DEP0207). const { ending } = stream._writableState; - if (!ending && !stream.aborted) { - state.flags |= STREAM_FLAGS_ABORTED; - stream.emit('aborted'); - } if (rstStreamStatus !== kNoRstStream) { // If the user already called .end() and writes are still draining, @@ -2174,6 +2167,14 @@ function closeStream(stream, code, rstStreamStatus = kSubmitRstStream) { } } + // Emit 'aborted' if the user hadn't ended the writable yet. This is + // unusual and doesn't match 'error' but is deprecated & preserved for + // backward compat (DEP0207). + if (!ending && !stream.aborted) { + state.flags |= STREAM_FLAGS_ABORTED; + stream.emit('aborted'); + } + if (type === NGHTTP2_SESSION_CLIENT) { if (onClientStreamCloseChannel.hasSubscribers) { onClientStreamCloseChannel.publish({ stream }); diff --git a/test/parallel/test-http2-close-aborted-destroy.js b/test/parallel/test-http2-close-aborted-destroy.js new file mode 100644 index 000000000000..61abafed1944 --- /dev/null +++ b/test/parallel/test-http2-close-aborted-destroy.js @@ -0,0 +1,32 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const http2 = require('http2'); +const { NGHTTP2_CANCEL } = http2.constants; + +const server = http2.createServer(); + +server.on('stream', common.mustCall((stream) => { + stream.respond(); + stream.on('error', common.expectsError({ + code: 'ERR_HTTP2_STREAM_ABORTED', + })); + stream.on('close', common.mustCall(() => { + assert.strictEqual(stream.rstCode, NGHTTP2_CANCEL); + server.close(); + })); +})); + +server.listen(0, common.mustCall(() => { + const client = http2.connect(`http://localhost:${server.address().port}`); + const req = client.request({ ':method': 'POST' }); + + req.on('aborted', common.mustCall(() => req.destroy())); + req.on('close', common.mustCall(() => client.close())); + req.on('response', common.mustCall(() => { + req.close(NGHTTP2_CANCEL); + })); +}));