Skip to content

Commit 2a56d7e

Browse files
committed
v8: add setHeapSnapshotNearHeapLimit
1 parent ab89024 commit 2a56d7e

11 files changed

+246
-7
lines changed

doc/api/v8.md

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

359+
## `v8.setHeapSnapshotNearHeapLimit(limit)`
360+
361+
<!-- YAML
362+
added: REPLACEME
363+
-->
364+
365+
> Stability: 1 - Experimental
366+
367+
The API is a no-op if `--heapsnapshot-near-heap-limit` is already set from the
368+
command line or the API is called twice. And `limit` must be greater than or
369+
equal to 1. See [`--heapsnapshot-near-heap-limit`][] for more information.
370+
359371
## Serialization API
360372

361373
The serialization API provides means of serializing JavaScript values in a way
@@ -1020,6 +1032,7 @@ Returns true if the Node.js instance is run to build a snapshot.
10201032
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
10211033
[Hook Callbacks]: #hook-callbacks
10221034
[V8]: https://developers.google.com/v8/
1035+
[`--heapsnapshot-near-heap-limit`]: cli.md#--heapsnapshot-near-heap-limitmax_count
10231036
[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
10241037
[`Buffer`]: buffer.md
10251038
[`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/env.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,8 @@ Environment::Environment(IsolateData* isolate_data,
720720
inspector_host_port_ = std::make_shared<ExclusiveAccess<HostPort>>(
721721
options_->debug_options().host_port);
722722

723+
set_heap_snapshot_near_heap_limit(options_->heap_snapshot_near_heap_limit);
724+
723725
if (!(flags_ & EnvironmentFlags::kOwnsProcessState)) {
724726
set_abort_on_uncaught_exception(false);
725727
}
@@ -834,7 +836,7 @@ Environment::~Environment() {
834836
// FreeEnvironment() should have set this.
835837
CHECK(is_stopping());
836838

837-
if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) {
839+
if (get_heap_snapshot_near_heap_limit() > heap_limit_snapshot_taken_) {
838840
isolate_->RemoveNearHeapLimitCallback(Environment::NearHeapLimitCallback,
839841
0);
840842
}
@@ -1982,7 +1984,7 @@ size_t Environment::NearHeapLimitCallback(void* data,
19821984
// Don't take more snapshots than the number specified by
19831985
// --heapsnapshot-near-heap-limit.
19841986
if (env->heap_limit_snapshot_taken_ <
1985-
env->options_->heap_snapshot_near_heap_limit) {
1987+
env->get_heap_snapshot_near_heap_limit()) {
19861988
env->isolate()->AddNearHeapLimitCallback(NearHeapLimitCallback, env);
19871989
}
19881990

src/env.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,14 @@ class Environment : public MemoryRetainer {
14891489
template <typename T>
14901490
void ForEachBaseObject(T&& iterator);
14911491

1492+
inline int64_t get_heap_snapshot_near_heap_limit() {
1493+
return heap_snapshot_near_heap_limit_;
1494+
}
1495+
1496+
inline void set_heap_snapshot_near_heap_limit(int64_t limit) {
1497+
heap_snapshot_near_heap_limit_ = limit;
1498+
}
1499+
14921500
private:
14931501
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
14941502
const char* errmsg);
@@ -1547,6 +1555,7 @@ class Environment : public MemoryRetainer {
15471555

15481556
bool is_processing_heap_limit_callback_ = false;
15491557
int64_t heap_limit_snapshot_taken_ = 0;
1558+
int64_t heap_snapshot_near_heap_limit_ = 0;
15501559

15511560
uint32_t module_id_counter_ = 0;
15521561
uint32_t script_id_counter_ = 0;

src/node.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ static void AtomicsWaitCallback(Isolate::AtomicsWaitEvent event,
273273
void Environment::InitializeDiagnostics() {
274274
isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
275275
Environment::BuildEmbedderGraph, this);
276-
if (options_->heap_snapshot_near_heap_limit > 0) {
276+
if (get_heap_snapshot_near_heap_limit() > 0) {
277277
isolate_->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback,
278278
this);
279279
}

src/node_v8.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@ 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->get_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->set_heap_snapshot_near_heap_limit(limit);
168+
isolate->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback, env);
169+
}
170+
}
171+
160172
void UpdateHeapStatisticsBuffer(const FunctionCallbackInfo<Value>& args) {
161173
BindingData* data = Environment::GetBindingData<BindingData>(args);
162174
HeapStatistics s;
@@ -212,6 +224,10 @@ void Initialize(Local<Object> target,
212224

213225
SetMethodNoSideEffect(
214226
context, target, "cachedDataVersionTag", CachedDataVersionTag);
227+
SetMethodNoSideEffect(context,
228+
target,
229+
"setHeapSnapshotNearHeapLimit",
230+
SetHeapSnapshotNearHeapLimit);
215231
SetMethod(context,
216232
target,
217233
"updateHeapStatisticsBuffer",
@@ -267,6 +283,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
267283
registry->Register(UpdateHeapCodeStatisticsBuffer);
268284
registry->Register(UpdateHeapSpaceStatisticsBuffer);
269285
registry->Register(SetFlagsFromString);
286+
registry->Register(SetHeapSnapshotNearHeapLimit);
270287
}
271288

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

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)