Skip to content

Commit e1b6b33

Browse files
Michael Lehenbauertargos
Michael Lehenbauer
authored andcommitted
http2: fix session memory accounting after pausing
The ability to pause input processing was added in 8a4a193 but introduced a session memory accounting mismatch leading to potential NGHTTP2_ENHANCE_YOUR_CALM errors. After pausing (https://github.com/nodejs/node/blob/f36331c1bfa4c4c202346b05dc3bd672f653e4df/src/node_http2.cc#L871), the early return on line 873 skips the DecrementCurrentSessionMemory(stream_buf_.len) call below (line 878). When we later finished processing the input chunk (https://github.com/nodejs/node/blob/f36331c1bfa4c4c202346b05dc3bd672f653e4df/src/node_http2.cc#L1858), we were calling DecrementCurrentSessionMemory(stream_buf_offset_) [line 1875] which was a no-op since we just set stream_buf_offset_ to 0 [line 1873]. The correct amount to decrement by is still stream_buf_.len, since that's the amount we skipped previously (line 878). Fixes: #29223 Refs: 164ac5b PR-URL: #30684 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: David Carlier <[email protected]>
1 parent 43f9137 commit e1b6b33

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

src/node_http2.cc

+4-1
Original file line numberDiff line numberDiff line change
@@ -1911,7 +1911,10 @@ void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
19111911
nread = buf.size();
19121912
stream_buf_offset_ = 0;
19131913
stream_buf_ab_.Reset();
1914-
DecrementCurrentSessionMemory(stream_buf_offset_);
1914+
1915+
// We have now fully processed the stream_buf_ input chunk (by moving the
1916+
// remaining part into buf, which will be accounted for below).
1917+
DecrementCurrentSessionMemory(stream_buf_.len);
19151918
}
19161919

19171920
// Shrink to the actual amount of used data.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto)
4+
common.skip('missing crypto');
5+
const fixtures = require('../common/fixtures');
6+
const http2 = require('http2');
7+
8+
// Regression test for https://github.com/nodejs/node/issues/29223.
9+
// There was a "leak" in the accounting of session memory leading
10+
// to streams eventually failing with NGHTTP2_ENHANCE_YOUR_CALM.
11+
12+
const server = http2.createSecureServer({
13+
key: fixtures.readKey('agent2-key.pem'),
14+
cert: fixtures.readKey('agent2-cert.pem'),
15+
});
16+
17+
// Simple server that sends 200k and closes the stream.
18+
const data200k = 'a'.repeat(200 * 1024);
19+
server.on('stream', (stream) => {
20+
stream.write(data200k);
21+
stream.end();
22+
});
23+
24+
server.listen(0, common.mustCall(() => {
25+
const client = http2.connect(`https://localhost:${server.address().port}`, {
26+
ca: fixtures.readKey('agent2-cert.pem'),
27+
servername: 'agent2',
28+
29+
// Set maxSessionMemory to 1MB so the leak causes errors faster.
30+
maxSessionMemory: 1
31+
});
32+
33+
// Repeatedly create a new stream and read the incoming data. Even though we
34+
// only have one stream active at a time, prior to the fix for #29223,
35+
// session memory would steadily increase and we'd eventually hit the 1MB
36+
// maxSessionMemory limit and get NGHTTP2_ENHANCE_YOUR_CALM errors trying to
37+
// create new streams.
38+
let streamsLeft = 50;
39+
function newStream() {
40+
const stream = client.request({ ':path': '/' });
41+
42+
stream.on('data', () => { });
43+
44+
stream.on('close', () => {
45+
if (streamsLeft-- > 0) {
46+
newStream();
47+
} else {
48+
client.destroy();
49+
server.close();
50+
}
51+
});
52+
}
53+
54+
newStream();
55+
}));

0 commit comments

Comments
 (0)