Skip to content

Commit 1bfcf81

Browse files
murgatroid99targos
authored andcommitted
http2: allow streams to complete gracefully after goaway
A detailed analysis of the cause of this bug is in my linked comment on the corresponding issue. The primary fix is the new setImmediate call in Http2Stream#_destroy, which prevents a re-entrant call into Http2Session::SendPendingData when sending trailers after the Http2Session has been shut down, allowing the trailer data to be flushed properly before the socket is closed. As a result of this change, writes can be initiated later in the lifetime of the Http2Session. So, when a JSStreamSocket is used as the underlying socket reference for an Http2Session, it needs to be able to accept write calls after it is closed. In addition, now that outgoing data can be flushed differently after a session is closed, in two tests clients receive errors that they previously did not receive. I believe the new errors are more correct, so I changed the tests to match. Fixes: #42713 Refs: #42713 (comment) PR-URL: #50202 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent 788714b commit 1bfcf81

5 files changed

+23
-10
lines changed

lib/internal/http2/core.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -2358,8 +2358,11 @@ class Http2Stream extends Duplex {
23582358
// This notifies the session that this stream has been destroyed and
23592359
// gives the session the opportunity to clean itself up. The session
23602360
// will destroy if it has been closed and there are no other open or
2361-
// pending streams.
2362-
session[kMaybeDestroy]();
2361+
// pending streams. Delay with setImmediate so we don't do it on the
2362+
// nghttp2 stack.
2363+
setImmediate(() => {
2364+
session[kMaybeDestroy]();
2365+
});
23632366
callback(err);
23642367
}
23652368
// The Http2Stream can be destroyed if it has closed and if the readable

lib/internal/js_stream_socket.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,13 @@ class JSStreamSocket extends Socket {
179179
// anything. doClose will call finishWrite with ECANCELED for us shortly.
180180
this[kCurrentWriteRequest] = req; // Store req, for doClose to cancel
181181
return 0;
182+
} else if (this._handle === null) {
183+
// If this._handle is already null, there is nothing left to do with a
184+
// pending write request, so we discard it.
185+
return 0;
182186
}
183187

184188
const handle = this._handle;
185-
assert(handle !== null);
186189

187190
const self = this;
188191

test/parallel/test-h2-large-header-cause-client-to-hangup.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const http2 = require('http2');
77
const assert = require('assert');
88
const {
99
DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE,
10-
NGHTTP2_CANCEL,
10+
NGHTTP2_FRAME_SIZE_ERROR,
1111
} = http2.constants;
1212

1313
const headerSize = DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE;
@@ -28,11 +28,17 @@ server.listen(0, common.mustCall(() => {
2828
function send() {
2929
const stream = clientSession.request({ ':path': '/' });
3030
stream.on('close', common.mustCall(() => {
31-
assert.strictEqual(stream.rstCode, NGHTTP2_CANCEL);
31+
assert.strictEqual(stream.rstCode, NGHTTP2_FRAME_SIZE_ERROR);
3232
clearTimeout(timer);
3333
server.close();
3434
}));
3535

36+
stream.on('error', common.expectsError({
37+
code: 'ERR_HTTP2_STREAM_ERROR',
38+
name: 'Error',
39+
message: 'Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR'
40+
}));
41+
3642
stream.end();
3743
}
3844
}));

test/parallel/test-http2-exceeds-server-trailer-size.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ server.listen(0, () => {
4343
const clientStream = clientSession.request();
4444

4545
clientStream.on('close', common.mustCall());
46-
// These events mustn't be called once the frame size error is from the server
46+
clientStream.on('error', common.expectsError({
47+
code: 'ERR_HTTP2_STREAM_ERROR',
48+
name: 'Error',
49+
message: 'Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR'
50+
}));
51+
// This event mustn't be called once the frame size error is from the server
4752
clientStream.on('frameError', common.mustNotCall());
48-
clientStream.on('error', common.mustNotCall());
4953

5054
clientStream.end();
5155
});

test/known_issues/test-http2-trailers-after-session-close.js test/parallel/test-http2-trailers-after-session-close.js

-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
// Fixes: https://github.com/nodejs/node/issues/42713
44
const common = require('../common');
55
if (!common.hasCrypto) {
6-
// Remove require('assert').fail when issue is fixed and test
7-
// is moved out of the known_issues directory.
8-
require('assert').fail('missing crypto');
96
common.skip('missing crypto');
107
}
118
const assert = require('assert');

0 commit comments

Comments
 (0)