Skip to content

Commit 9289374

Browse files
ywave620aduh95
authored andcommitted
http2: fix memory leak caused by premature listener removing
Http2Session should always call ondone into JS to detach the handle. In some case, ondone is defered to be called by the StreamListener through WriteWrap, we should be careful of this before getting rid of the StreamListener. PR-URL: #55966 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]>
1 parent f99f95f commit 9289374

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

src/node_http2.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -803,13 +803,15 @@ void Http2Session::Close(uint32_t code, bool socket_closed) {
803803
CHECK_EQ(nghttp2_session_terminate_session(session_.get(), code), 0);
804804
SendPendingData();
805805
} else if (stream_ != nullptr) {
806+
// so that the previous listener of the socket, typically, JS code of a
807+
// (tls) socket will be notified of any activity later
806808
stream_->RemoveStreamListener(this);
807809
}
808810

809811
set_destroyed();
810812

811813
// If we are writing we will get to make the callback in OnStreamAfterWrite.
812-
if (!is_write_in_progress()) {
814+
if (!is_write_in_progress() || !stream_) {
813815
Debug(this, "make done session callback");
814816
HandleScope scope(env()->isolate());
815817
MakeCallback(env()->ondone_string(), 0, nullptr);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
// Flags: --expose-gc
3+
4+
const common = require('../common');
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
const http2 = require('http2');
8+
const tls = require('tls');
9+
const fixtures = require('../common/fixtures');
10+
const assert = require('assert');
11+
12+
const registry = new FinalizationRegistry(common.mustCall((name) => {
13+
assert(name, 'session');
14+
}));
15+
16+
const server = http2.createSecureServer({
17+
key: fixtures.readKey('agent1-key.pem'),
18+
cert: fixtures.readKey('agent1-cert.pem'),
19+
});
20+
21+
let firstServerStream;
22+
23+
24+
server.on('secureConnection', (s) => {
25+
console.log('secureConnection');
26+
s.on('end', () => {
27+
console.log(s.destroyed); // false !!
28+
s.destroy();
29+
firstServerStream.session.destroy();
30+
31+
firstServerStream = null;
32+
33+
setImmediate(() => {
34+
global.gc();
35+
global.gc();
36+
37+
server.close();
38+
});
39+
});
40+
});
41+
42+
server.on('session', (s) => {
43+
registry.register(s, 'session');
44+
});
45+
46+
server.on('stream', (stream) => {
47+
console.log('stream...');
48+
stream.write('a'.repeat(1024));
49+
firstServerStream = stream;
50+
setImmediate(() => console.log('Draining setImmediate after writing'));
51+
});
52+
53+
54+
server.listen(() => {
55+
client();
56+
});
57+
58+
59+
const h2fstStream = [
60+
'UFJJICogSFRUUC8yLjANCg0KU00NCg0K',
61+
// http message (1st stream:)
62+
'AAAABAAAAAAA',
63+
'AAAPAQUAAAABhIJBiqDkHROdCbjwHgeG',
64+
];
65+
function client() {
66+
const client = tls.connect({
67+
port: server.address().port,
68+
host: 'localhost',
69+
rejectUnauthorized: false,
70+
ALPNProtocols: ['h2']
71+
}, () => {
72+
client.end(Buffer.concat(h2fstStream.map((s) => Buffer.from(s, 'base64'))), (err) => {
73+
assert.ifError(err);
74+
});
75+
});
76+
77+
client.on('error', (error) => {
78+
console.error('Connection error:', error);
79+
});
80+
}

0 commit comments

Comments
 (0)