Skip to content

Commit e094c1c

Browse files
IlyasShabiszegedi
authored andcommitted
[heap] Restore original heap size (#371)
* restore original heap size
1 parent 7395308 commit e094c1c

3 files changed

Lines changed: 120 additions & 0 deletions

File tree

bindings/profilers/heap.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ struct HeapProfilerState {
9595
}
9696
if (isolate) {
9797
isolate->AddNearHeapLimitCallback(&NearHeapLimit, nullptr);
98+
// Restore the original heap limit once live old-generation usage falls
99+
// below 90% of the original limit. The threshold controls when V8
100+
// restores the limit, not the restored limit size.
101+
constexpr double kHeapLimitRestoreThreshold = 0.90;
102+
isolate->AutomaticallyRestoreInitialHeapLimit(kHeapLimitRestoreThreshold);
98103
callbackInstalled = true;
99104
}
100105
}

ts/test/oom-restore-heap-limit.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2026 Datadog, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
'use strict';
18+
19+
import * as v8 from 'v8';
20+
21+
import {heap} from '../src/index';
22+
23+
const MB = 1024 * 1024;
24+
const LIMIT_TOLERANCE = 16 * MB;
25+
const CHUNK_SIZE = 4 * MB;
26+
const gc = (global as typeof globalThis & {gc?: () => void}).gc;
27+
28+
function heapLimit() {
29+
return v8.getHeapStatistics().heap_size_limit;
30+
}
31+
32+
function allocateChunk() {
33+
const chunk = new Array<number>(CHUNK_SIZE / 8);
34+
for (let i = 0; i < chunk.length; i++) {
35+
chunk[i] = i + 0.1;
36+
}
37+
return chunk;
38+
}
39+
40+
async function main() {
41+
if (!gc) {
42+
throw new Error('This test must be run with --expose-gc.');
43+
}
44+
45+
heap.start(MB, 64);
46+
try {
47+
heap.monitorOutOfMemory(64 * MB, 1, false);
48+
49+
const initialLimit = heapLimit();
50+
const retained: number[][] = [];
51+
52+
while (
53+
heapLimit() <= initialLimit + LIMIT_TOLERANCE &&
54+
retained.length < 64
55+
) {
56+
retained.push(allocateChunk());
57+
}
58+
59+
const increasedLimit = heapLimit();
60+
if (increasedLimit <= initialLimit + LIMIT_TOLERANCE) {
61+
throw new Error(`Heap limit did not increase from ${initialLimit}.`);
62+
}
63+
64+
retained.length = 0;
65+
for (let i = 0; i < 100; i++) {
66+
gc();
67+
if (heapLimit() <= initialLimit + LIMIT_TOLERANCE) {
68+
return;
69+
}
70+
await new Promise(resolve => setTimeout(resolve, 20));
71+
}
72+
73+
throw new Error(
74+
`Heap limit increased to ${increasedLimit} but did not restore to ` +
75+
`${initialLimit}.`,
76+
);
77+
} finally {
78+
heap.stop();
79+
}
80+
}
81+
82+
main().catch(err => {
83+
console.error(err.stack || err.message);
84+
process.exitCode = 1;
85+
});

ts/test/test-heap-profiler.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,36 @@ describe('HeapProfiler', () => {
331331
});
332332

333333
describe('OOMMonitoring', () => {
334+
it('should restore heap limit after v8 recovers from OOM', async function () {
335+
this.timeout(30000);
336+
337+
const proc = fork(path.join(__dirname, 'oom-restore-heap-limit.js'), {
338+
execArgv: ['--expose-gc', '--max-old-space-size=64'],
339+
silent: true,
340+
});
341+
let output = '';
342+
343+
proc.stdout?.on('data', chunk => {
344+
output += chunk;
345+
});
346+
proc.stderr?.on('data', chunk => {
347+
output += chunk;
348+
});
349+
350+
await new Promise<void>((resolve, reject) => {
351+
proc.on('error', reject);
352+
proc.on('exit', code => {
353+
if (code === 0) {
354+
resolve();
355+
} else {
356+
reject(
357+
new Error(`oom-restore-heap-limit exited with ${code}\n${output}`),
358+
);
359+
}
360+
});
361+
});
362+
});
363+
334364
it('should call external process upon OOM', async function () {
335365
// this test is very slow on some configs (asan/valgrind)
336366
this.timeout(20000);

0 commit comments

Comments
 (0)