src: let http2 streams end after session close

After the stream has been marked as closed by the nghttp2 stack, there
might be still pending data to be sent: trailing headers is an example
of this. In that case, avoid reentering the nghttp2 stack synchronously
to allow the data to be written before actually closing the stream.

Fixes: https://github.com/nodejs/node/issues/42713
PR-URL: https://github.com/nodejs/node/pull/45153
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
Santiago Gimeno authored and GitHub committed 2022-10-27 09:34:45 +00:00
1 parent 466d36ebac
commit 71bdecb908
2 files changed
+60

No files matched your search

+11
View File
@@ -1120,6 +1120,17 @@ int Http2Session::OnStreamClose(nghttp2_session* handle,
if (!stream || stream->is_destroyed())
return 0;
// Don't close synchronously in case there's pending data to be written. This
// may happen when writing trailing headers.
if (code == NGHTTP2_NO_ERROR && nghttp2_session_want_write(handle) &&
!env->is_stopping()) {
env->SetImmediate([handle, id, code, user_data](Environment* env) {
OnStreamClose(handle, id, code, user_data);
});
return 0;
}
stream->Close(code);
// It is possible for the stream close to occur before the stream is
@@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
} = http2.constants;
const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
server.close();
stream.session.close();
stream.on('wantTrailers', common.mustCall(() => {
stream.sendTrailers({ xyz: 'abc' });
}));
stream.respond({ [HTTP2_HEADER_STATUS]: 200 }, { waitForTrailers: true });
stream.write('some data');
stream.end();
}));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
const client = http2.connect(`http://localhost:${port}`);
client.socket.on('close', common.mustCall());
const req = client.request({
[HTTP2_HEADER_PATH]: '/',
[HTTP2_HEADER_METHOD]: 'POST'
});
req.end();
req.on('response', common.mustCall());
let data = '';
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', common.mustCall(() => {
assert.strictEqual(data, 'some data');
}));
req.on('trailers', common.mustCall((headers) => {
assert.strictEqual(headers.xyz, 'abc');
}));
req.on('close', common.mustCall());
}));