Files
node/test/parallel/test-http2-session-destroy-stream-handler.js
Sankalp Thakur 0e32ee2114 http2: fix onread assert when destroying session from stream handler
When session.destroy() runs from a 'stream' handler, MakeCallback drains
nextTick while nghttp2 is still inside mem_recv. Close is deferred for
that window (see #64166), so later HEADERS in the same buffer created
C++ streams without a JS wrapper or onread, and DATA delivery aborted
with Assertion failed: onread->IsFunction().

- Reject new streams while the session is closing
- Destroy the C++ handle if on_headers runs after JS destroy
- Drop DATA when onread is not installed (defensive)

Fixes: https://github.com/nodejs/node/issues/64850
Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65116
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
2026-09-14 11:49:43 +02:00

61 lines
1.5 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');
// Regression test for https://github.com/nodejs/node/issues/64850
//
// Destroying the session from a 'stream' handler runs (via nextTick drained
// from MakeCallback) while nghttp2 is still inside mem_recv. Close is deferred
// for that window; later HEADERS/DATA in the same buffer must not abort with
// Assertion failed: onread->IsFunction().
const STREAMS = 8;
const BODY = Buffer.alloc(2048, 'a');
const ROUNDS = 40;
const server = http2.createServer({
settings: { maxConcurrentStreams: 4 },
});
server.on('session', (session) => session.on('error', () => {}));
server.on('stream', (stream) => {
stream.on('error', () => {});
stream.session.destroy();
});
server.listen(0, '127.0.0.1', common.mustCall(() => {
const port = server.address().port;
const origin = `http://127.0.0.1:${port}`;
let remaining = ROUNDS;
const round = () => {
if (remaining-- <= 0) {
server.close();
return;
}
const session = http2.connect(origin);
session.on('error', () => {});
session.on('close', () => setImmediate(round));
session.on('connect', () => {
for (let i = 0; i < STREAMS; i++) {
const stream = session.request({
':path': `/${i}`,
':method': 'POST',
});
stream.on('error', () => {});
stream.resume();
stream.end(BODY);
}
});
};
round();
}));