Skip to content

Commit c3a90c4

Browse files
ywave620richardlau
authored andcommitted
http2: fix memory leak when nghttp2 hd threshold is reached
Refs: #45295 PR-URL: #41502 Backport-PR-URL: #45310 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent d6f1d71 commit c3a90c4

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

src/node_http2.cc

+20
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,21 @@ int Http2Session::OnInvalidFrame(nghttp2_session* handle,
923923
return 0;
924924
}
925925

926+
// Remove the headers reference.
927+
// Implicitly calls nghttp2_rcbuf_decref
928+
void Http2Session::DecrefHeaders(const nghttp2_frame* frame) {
929+
int32_t id = GetFrameID(frame);
930+
BaseObjectPtr<Http2Stream> stream = FindStream(id);
931+
932+
if (stream && !stream->is_destroyed() && stream->headers_count() > 0) {
933+
Debug(this, "freeing headers for stream %d", id);
934+
stream->ClearHeaders();
935+
CHECK_EQ(stream->headers_count(), 0);
936+
DecrementCurrentSessionMemory(stream->current_headers_length_);
937+
stream->current_headers_length_ = 0;
938+
}
939+
}
940+
926941
// If nghttp2 is unable to send a queued up frame, it will call this callback
927942
// to let us know. If the failure occurred because we are in the process of
928943
// closing down the session or stream, we go ahead and ignore it. We don't
@@ -943,6 +958,11 @@ int Http2Session::OnFrameNotSent(nghttp2_session* handle,
943958
error_code == NGHTTP2_ERR_STREAM_CLOSED ||
944959
error_code == NGHTTP2_ERR_STREAM_CLOSING ||
945960
session->js_fields_->frame_error_listener_count == 0) {
961+
// Nghttp2 contains header limit of 65536. When this value is exceeded the
962+
// pipeline is stopped and we should remove the current headers reference
963+
// to destroy the session completely.
964+
// Further information see: https://github.com/nodejs/node/issues/35233
965+
session->DecrefHeaders(frame);
946966
return 0;
947967
}
948968

src/node_http2.h

+6
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ class Http2Stream : public AsyncWrap,
401401
size_t i = 0;
402402
for (const auto& header : current_headers_ )
403403
fn(header, i++);
404+
ClearHeaders();
405+
}
406+
407+
void ClearHeaders() {
404408
current_headers_.clear();
405409
}
406410

@@ -784,6 +788,8 @@ class Http2Session : public AsyncWrap,
784788
void HandleAltSvcFrame(const nghttp2_frame* frame);
785789
void HandleOriginFrame(const nghttp2_frame* frame);
786790

791+
void DecrefHeaders(const nghttp2_frame* frame);
792+
787793
// nghttp2 callbacks
788794
static int OnBeginHeadersCallback(
789795
nghttp2_session* session,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
const h2 = require('http2');
7+
8+
const server = h2.createServer();
9+
10+
server.on('stream', common.mustNotCall());
11+
server.on('error', common.mustNotCall());
12+
13+
server.listen(0, common.mustCall(() => {
14+
15+
// Setting the maxSendHeaderBlockLength > nghttp2 threshold
16+
// cause a 'sessionError' and no memory leak when session destroy
17+
const options = {
18+
maxSendHeaderBlockLength: 100000
19+
};
20+
21+
const client = h2.connect(`http://localhost:${server.address().port}`,
22+
options);
23+
client.on('error', common.expectsError({
24+
code: 'ERR_HTTP2_SESSION_ERROR',
25+
name: 'Error',
26+
message: 'Session closed with error code 9'
27+
}));
28+
29+
const req = client.request({
30+
// Greater than 65536 bytes
31+
'test-header': 'A'.repeat(90000)
32+
});
33+
req.on('response', common.mustNotCall());
34+
35+
req.on('close', common.mustCall(() => {
36+
client.close();
37+
server.close();
38+
}));
39+
40+
req.on('error', common.expectsError({
41+
code: 'ERR_HTTP2_SESSION_ERROR',
42+
name: 'Error',
43+
message: 'Session closed with error code 9'
44+
}));
45+
req.end();
46+
}));

0 commit comments

Comments
 (0)