Skip to content

Commit cde811c

Browse files
committed
v8: add setHeapSnapshotNearHeapLimit
1 parent ab89024 commit cde811c

File tree

6 files changed

+226
-4
lines changed

6 files changed

+226
-4
lines changed

doc/api/v8.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,16 @@ if (isMainThread) {
356356
}
357357
```
358358

359+
## `v8.setHeapSnapshotNearHeapLimit(limit)`
360+
361+
<!-- YAML
362+
added: REPLACEME
363+
-->
364+
365+
> Stability: 1 - Experimental
366+
367+
See [`--heapsnapshot-near-heap-limit`][].
368+
359369
## Serialization API
360370

361371
The serialization API provides means of serializing JavaScript values in a way
@@ -1020,6 +1030,7 @@ Returns true if the Node.js instance is run to build a snapshot.
10201030
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
10211031
[Hook Callbacks]: #hook-callbacks
10221032
[V8]: https://developers.google.com/v8/
1033+
[`--heapsnapshot-near-heap-limit`]: cli.md#--heapsnapshot-near-heap-limitmax_count
10231034
[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
10241035
[`Buffer`]: buffer.md
10251036
[`DefaultDeserializer`]: #class-v8defaultdeserializer

lib/v8.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const {
3333
} = primordials;
3434

3535
const { Buffer } = require('buffer');
36-
const { validateString } = require('internal/validators');
36+
const { validateString, validateNumber } = require('internal/validators');
3737
const {
3838
Serializer,
3939
Deserializer
@@ -59,7 +59,7 @@ const {
5959
} = internalBinding('heap_utils');
6060
const { HeapSnapshotStream } = require('internal/heap_utils');
6161
const promiseHooks = require('internal/promise_hooks');
62-
62+
const { getOptionValue } = require('internal/options');
6363
/**
6464
* Generates a snapshot of the current V8 heap
6565
* and writes it to a JSON file.
@@ -95,6 +95,7 @@ const {
9595
updateHeapStatisticsBuffer,
9696
updateHeapSpaceStatisticsBuffer,
9797
updateHeapCodeStatisticsBuffer,
98+
setHeapSnapshotNearHeapLimit: _setHeapSnapshotNearHeapLimit,
9899

99100
// Properties for heap statistics buffer extraction.
100101
kTotalHeapSizeIndex,
@@ -226,6 +227,18 @@ function getHeapCodeStatistics() {
226227
};
227228
}
228229

230+
let called = false;
231+
function setHeapSnapshotNearHeapLimit(limit) {
232+
validateNumber(limit, 'limit', 1);
233+
const value = getOptionValue('--heapsnapshot-near-heap-limit');
234+
// Can not be called twice.
235+
if (value > 0 || called) {
236+
return;
237+
}
238+
called = true;
239+
_setHeapSnapshotNearHeapLimit(limit);
240+
}
241+
229242
/* V8 serialization API */
230243

231244
/* JS methods for the base objects */
@@ -387,5 +400,6 @@ module.exports = {
387400
serialize,
388401
writeHeapSnapshot,
389402
promiseHooks,
390-
startupSnapshot
403+
startupSnapshot,
404+
setHeapSnapshotNearHeapLimit,
391405
};

src/node_v8.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,19 @@ void CachedDataVersionTag(const FunctionCallbackInfo<Value>& args) {
157157
args.GetReturnValue().Set(result);
158158
}
159159

160+
void SetHeapSnapshotNearHeapLimit(const FunctionCallbackInfo<Value>& args) {
161+
CHECK(args[0]->IsNumber());
162+
Environment* env = Environment::GetCurrent(args);
163+
if (env->options()->heap_snapshot_near_heap_limit == 0) {
164+
Isolate* const isolate = args.GetIsolate();
165+
int64_t limit = args[0].As<v8::Number>()->Value();
166+
CHECK_GT(limit, 0);
167+
env->options()->heap_snapshot_near_heap_limit = limit;
168+
isolate->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback,
169+
env);
170+
}
171+
}
172+
160173
void UpdateHeapStatisticsBuffer(const FunctionCallbackInfo<Value>& args) {
161174
BindingData* data = Environment::GetBindingData<BindingData>(args);
162175
HeapStatistics s;
@@ -212,6 +225,10 @@ void Initialize(Local<Object> target,
212225

213226
SetMethodNoSideEffect(
214227
context, target, "cachedDataVersionTag", CachedDataVersionTag);
228+
SetMethodNoSideEffect(context,
229+
target,
230+
"setHeapSnapshotNearHeapLimit",
231+
SetHeapSnapshotNearHeapLimit);
215232
SetMethod(context,
216233
target,
217234
"updateHeapStatisticsBuffer",
@@ -267,6 +284,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
267284
registry->Register(UpdateHeapCodeStatisticsBuffer);
268285
registry->Register(UpdateHeapSpaceStatisticsBuffer);
269286
registry->Register(SetFlagsFromString);
287+
registry->Register(SetHeapSnapshotNearHeapLimit);
270288
}
271289

272290
} // namespace v8_utils
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copy from test-heapsnapshot-near-heap-limit-worker.js
2+
'use strict';
3+
4+
require('../common');
5+
const tmpdir = require('../common/tmpdir');
6+
const assert = require('assert');
7+
const { spawnSync } = require('child_process');
8+
const fixtures = require('../common/fixtures');
9+
const fs = require('fs');
10+
11+
if (process.env.isChild === '1') {
12+
require(fixtures.path('workload', 'grow-worker.js'));
13+
const v8 = require('v8');
14+
v8.setHeapSnapshotNearHeapLimit(1);
15+
return;
16+
}
17+
const env = {
18+
...process.env,
19+
NODE_DEBUG_NATIVE: 'diagnostics'
20+
};
21+
22+
{
23+
tmpdir.refresh();
24+
const child = spawnSync(process.execPath, [
25+
__filename,
26+
], {
27+
cwd: tmpdir.path,
28+
env: {
29+
isChild: 1,
30+
TEST_SNAPSHOTS: 1,
31+
TEST_OLD_SPACE_SIZE: 50,
32+
...env
33+
}
34+
});
35+
console.log(child.stdout.toString());
36+
const stderr = child.stderr.toString();
37+
console.log(stderr);
38+
const risky = /Not generating snapshots because it's too risky/.test(stderr);
39+
if (!risky) {
40+
// There should be one snapshot taken and then after the
41+
// snapshot heap limit callback is popped, the OOM callback
42+
// becomes effective.
43+
assert(stderr.includes('ERR_WORKER_OUT_OF_MEMORY'));
44+
const list = fs.readdirSync(tmpdir.path)
45+
.filter((file) => file.endsWith('.heapsnapshot'));
46+
assert.strictEqual(list.length, 1);
47+
}
48+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copy from test-heapsnapshot-near-heap-limit.js
2+
'use strict';
3+
4+
const common = require('../common');
5+
6+
if (common.isPi) {
7+
common.skip('Too slow for Raspberry Pi devices');
8+
}
9+
10+
const tmpdir = require('../common/tmpdir');
11+
const assert = require('assert');
12+
const { spawnSync } = require('child_process');
13+
const fixtures = require('../common/fixtures');
14+
const fs = require('fs');
15+
const v8 = require('v8');
16+
17+
if (process.env.isChild === '1') {
18+
require(fixtures.path('workload', 'grow.js'));
19+
const count = ~~process.env.count || 1;
20+
for (let i = 0; i < count; i++) {
21+
v8.setHeapSnapshotNearHeapLimit(+process.env.limit);
22+
}
23+
return;
24+
}
25+
26+
const invalidValues = [-1, '', {}, NaN, undefined];
27+
let errorCount = 0;
28+
for (let i = 0; i < invalidValues.length; i++) {
29+
try {
30+
v8.setHeapSnapshotNearHeapLimit(invalidValues[i]);
31+
} catch (e) {
32+
console.log(e);
33+
errorCount++;
34+
}
35+
}
36+
assert.strictEqual(errorCount, invalidValues.length);
37+
38+
// Set twice
39+
v8.setHeapSnapshotNearHeapLimit(1);
40+
v8.setHeapSnapshotNearHeapLimit(2);
41+
42+
const env = {
43+
...process.env,
44+
NODE_DEBUG_NATIVE: 'diagnostics',
45+
isChild: 1,
46+
};
47+
48+
{
49+
console.log('\nTesting set by cmd option and api');
50+
tmpdir.refresh();
51+
const child = spawnSync(process.execPath, [
52+
'--trace-gc',
53+
'--heapsnapshot-near-heap-limit=1',
54+
'--max-old-space-size=50',
55+
fixtures.path('workload', 'grow.js'),
56+
], {
57+
cwd: tmpdir.path,
58+
env: {
59+
...env,
60+
limit: 1,
61+
},
62+
});
63+
console.log(child.stdout.toString());
64+
const stderr = child.stderr.toString();
65+
console.log(stderr);
66+
assert(common.nodeProcessAborted(child.status, child.signal),
67+
'process should have aborted, but did not');
68+
const list = fs.readdirSync(tmpdir.path)
69+
.filter((file) => file.endsWith('.heapsnapshot'));
70+
const risky = [...stderr.matchAll(
71+
/Not generating snapshots because it's too risky/g)].length;
72+
assert(list.length + risky > 0 && list.length <= 1,
73+
`Generated ${list.length} snapshots ` +
74+
`and ${risky} was too risky`);
75+
}
76+
77+
{
78+
console.log('\nTesting limit = 1');
79+
tmpdir.refresh();
80+
const child = spawnSync(process.execPath, [
81+
'--trace-gc',
82+
'--max-old-space-size=50',
83+
__filename,
84+
], {
85+
cwd: tmpdir.path,
86+
env: {
87+
...env,
88+
limit: 1,
89+
},
90+
});
91+
console.log(child.stdout.toString());
92+
const stderr = child.stderr.toString();
93+
console.log(stderr);
94+
assert(common.nodeProcessAborted(child.status, child.signal),
95+
'process should have aborted, but did not');
96+
const list = fs.readdirSync(tmpdir.path)
97+
.filter((file) => file.endsWith('.heapsnapshot'));
98+
const risky = [...stderr.matchAll(
99+
/Not generating snapshots because it's too risky/g)].length;
100+
assert(list.length + risky > 0 && list.length <= 1,
101+
`Generated ${list.length} snapshots ` +
102+
`and ${risky} was too risky`);
103+
}
104+
105+
{
106+
console.log('\nTesting limit = 3');
107+
tmpdir.refresh();
108+
const child = spawnSync(process.execPath, [
109+
'--trace-gc',
110+
'--max-old-space-size=50',
111+
__filename,
112+
], {
113+
cwd: tmpdir.path,
114+
env: {
115+
...env,
116+
limit: 3,
117+
},
118+
});
119+
console.log(child.stdout.toString());
120+
const stderr = child.stderr.toString();
121+
console.log(stderr);
122+
assert(common.nodeProcessAborted(child.status, child.signal),
123+
'process should have aborted, but did not');
124+
const list = fs.readdirSync(tmpdir.path)
125+
.filter((file) => file.endsWith('.heapsnapshot'));
126+
const risky = [...stderr.matchAll(
127+
/Not generating snapshots because it's too risky/g)].length;
128+
assert(list.length + risky > 0 && list.length <= 3,
129+
`Generated ${list.length} snapshots ` +
130+
`and ${risky} was too risky`);
131+
}

test/pummel/test-heapsnapshot-near-heap-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const env = {
7171
.filter((file) => file.endsWith('.heapsnapshot'));
7272
const risky = [...stderr.matchAll(
7373
/Not generating snapshots because it's too risky/g)].length;
74-
assert(list.length + risky > 0 && list.length <= 3,
74+
assert(list.length + risky > 0 && list.length <= 1,
7575
`Generated ${list.length} snapshots ` +
7676
`and ${risky} was too risky`);
7777
}

0 commit comments

Comments
 (0)