Skip to content

Commit

Permalink
src: let http2 streams end after session close
Browse files Browse the repository at this point in the history
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: #42713
PR-URL: #45153
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Paolo Insogna <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Rafael Gonzaga <[email protected]>
(cherry picked from commit 71bdecb)
  • Loading branch information
santigimeno authored and adamrdavid committed Nov 28, 2022
1 parent 25307a3 commit edb561c
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/node_http2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,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
Expand Down
49 changes: 49 additions & 0 deletions test/parallel/test-http2-trailers-after-session-close.js
Original file line number Diff line number Diff line change
@@ -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());
}));

0 comments on commit edb561c

Please sign in to comment.