Skip to content

Commit 0beedb7

Browse files
theanarkhjuanarbol
authored andcommitted
v8: add setHeapSnapshotNearHeapLimit
PR-URL: #44420 Refs: #33010 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent a021f19 commit 0beedb7

12 files changed

+292
-17
lines changed

doc/api/v8.md

+15
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,20 @@ if (isMainThread) {
343343
}
344344
```
345345

346+
## `v8.setHeapSnapshotNearHeapLimit(limit)`
347+
348+
<!-- YAML
349+
added: REPLACEME
350+
-->
351+
352+
> Stability: 1 - Experimental
353+
354+
* `limit` {integer}
355+
356+
The API is a no-op if `--heapsnapshot-near-heap-limit` is already set from the
357+
command line or the API is called more than once. `limit` must be a positive
358+
integer. See [`--heapsnapshot-near-heap-limit`][] for more information.
359+
346360
## Serialization API
347361

348362
The serialization API provides means of serializing JavaScript values in a way
@@ -987,6 +1001,7 @@ Returns true if the Node.js instance is run to build a snapshot.
9871001
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
9881002
[Hook Callbacks]: #hook-callbacks
9891003
[V8]: https://developers.google.com/v8/
1004+
[`--heapsnapshot-near-heap-limit`]: cli.md#--heapsnapshot-near-heap-limitmax_count
9901005
[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
9911006
[`Buffer`]: buffer.md
9921007
[`DefaultDeserializer`]: #class-v8defaultdeserializer

lib/v8.js

+17-2
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, validateUint32 } = require('internal/validators');
3737
const {
3838
Serializer,
3939
Deserializer
@@ -59,6 +59,7 @@ const {
5959
} = internalBinding('heap_utils');
6060
const { HeapSnapshotStream } = require('internal/heap_utils');
6161
const promiseHooks = require('internal/promise_hooks');
62+
const { getOptionValue } = require('internal/options');
6263

6364
/**
6465
* Generates a snapshot of the current V8 heap
@@ -95,6 +96,7 @@ const {
9596
updateHeapStatisticsBuffer,
9697
updateHeapSpaceStatisticsBuffer,
9798
updateHeapCodeStatisticsBuffer,
99+
setHeapSnapshotNearHeapLimit: _setHeapSnapshotNearHeapLimit,
98100

99101
// Properties for heap statistics buffer extraction.
100102
kTotalHeapSizeIndex,
@@ -223,6 +225,18 @@ function getHeapCodeStatistics() {
223225
};
224226
}
225227

228+
let heapSnapshotNearHeapLimitCallbackAdded = false;
229+
function setHeapSnapshotNearHeapLimit(limit) {
230+
validateUint32(limit, 'limit', 1);
231+
if (heapSnapshotNearHeapLimitCallbackAdded ||
232+
getOptionValue('--heapsnapshot-near-heap-limit') > 0
233+
) {
234+
return;
235+
}
236+
heapSnapshotNearHeapLimitCallbackAdded = true;
237+
_setHeapSnapshotNearHeapLimit(limit);
238+
}
239+
226240
/* V8 serialization API */
227241

228242
/* JS methods for the base objects */
@@ -384,5 +398,6 @@ module.exports = {
384398
serialize,
385399
writeHeapSnapshot,
386400
promiseHooks,
387-
startupSnapshot
401+
startupSnapshot,
402+
setHeapSnapshotNearHeapLimit,
388403
};

src/env-inl.h

+18
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,24 @@ v8::Local<v8::Context> Environment::context() const {
896896
return PersistentToLocal::Strong(context_);
897897
}
898898

899+
inline void Environment::set_heap_snapshot_near_heap_limit(uint32_t limit) {
900+
heap_snapshot_near_heap_limit_ = limit;
901+
}
902+
903+
inline void Environment::AddHeapSnapshotNearHeapLimitCallback() {
904+
DCHECK(!heapsnapshot_near_heap_limit_callback_added_);
905+
heapsnapshot_near_heap_limit_callback_added_ = true;
906+
isolate_->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback, this);
907+
}
908+
909+
inline void Environment::RemoveHeapSnapshotNearHeapLimitCallback(
910+
size_t heap_limit) {
911+
DCHECK(heapsnapshot_near_heap_limit_callback_added_);
912+
heapsnapshot_near_heap_limit_callback_added_ = false;
913+
isolate_->RemoveNearHeapLimitCallback(Environment::NearHeapLimitCallback,
914+
heap_limit);
915+
}
916+
899917
} // namespace node
900918

901919
// These two files depend on each other. Including base_object-inl.h after this

src/env.cc

+9-10
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,9 @@ Environment::Environment(IsolateData* isolate_data,
770770
inspector_host_port_ = std::make_shared<ExclusiveAccess<HostPort>>(
771771
options_->debug_options().host_port);
772772

773+
heap_snapshot_near_heap_limit_ =
774+
static_cast<uint32_t>(options_->heap_snapshot_near_heap_limit);
775+
773776
if (!(flags_ & EnvironmentFlags::kOwnsProcessState)) {
774777
set_abort_on_uncaught_exception(false);
775778
}
@@ -884,9 +887,8 @@ Environment::~Environment() {
884887
// FreeEnvironment() should have set this.
885888
CHECK(is_stopping());
886889

887-
if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) {
888-
isolate_->RemoveNearHeapLimitCallback(Environment::NearHeapLimitCallback,
889-
0);
890+
if (heapsnapshot_near_heap_limit_callback_added_) {
891+
RemoveHeapSnapshotNearHeapLimitCallback(0);
890892
}
891893

892894
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
@@ -2029,8 +2031,7 @@ size_t Environment::NearHeapLimitCallback(void* data,
20292031
Debug(env,
20302032
DebugCategory::DIAGNOSTICS,
20312033
"Not generating snapshots because it's too risky.\n");
2032-
env->isolate()->RemoveNearHeapLimitCallback(NearHeapLimitCallback,
2033-
initial_heap_limit);
2034+
env->RemoveHeapSnapshotNearHeapLimitCallback(initial_heap_limit);
20342035
// The new limit must be higher than current_heap_limit or V8 might
20352036
// crash.
20362037
return current_heap_limit + 1;
@@ -2050,17 +2051,15 @@ size_t Environment::NearHeapLimitCallback(void* data,
20502051

20512052
// Remove the callback first in case it's triggered when generating
20522053
// the snapshot.
2053-
env->isolate()->RemoveNearHeapLimitCallback(NearHeapLimitCallback,
2054-
initial_heap_limit);
2054+
env->RemoveHeapSnapshotNearHeapLimitCallback(initial_heap_limit);
20552055

20562056
heap::WriteSnapshot(env->isolate(), filename.c_str());
20572057
env->heap_limit_snapshot_taken_ += 1;
20582058

20592059
// Don't take more snapshots than the number specified by
20602060
// --heapsnapshot-near-heap-limit.
2061-
if (env->heap_limit_snapshot_taken_ <
2062-
env->options_->heap_snapshot_near_heap_limit) {
2063-
env->isolate()->AddNearHeapLimitCallback(NearHeapLimitCallback, env);
2061+
if (env->heap_limit_snapshot_taken_ < env->heap_snapshot_near_heap_limit_) {
2062+
env->AddHeapSnapshotNearHeapLimitCallback();
20642063
}
20652064

20662065
FPrintF(stderr, "Wrote snapshot to %s\n", filename.c_str());

src/env.h

+9-1
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,12 @@ class Environment : public MemoryRetainer {
14591459
template <typename T>
14601460
void ForEachBindingData(T&& iterator);
14611461

1462+
inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
1463+
1464+
inline void AddHeapSnapshotNearHeapLimitCallback();
1465+
1466+
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
1467+
14621468
private:
14631469
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
14641470
const char* errmsg);
@@ -1516,7 +1522,9 @@ class Environment : public MemoryRetainer {
15161522
std::string exec_path_;
15171523

15181524
bool is_processing_heap_limit_callback_ = false;
1519-
int64_t heap_limit_snapshot_taken_ = 0;
1525+
uint32_t heap_limit_snapshot_taken_ = 0;
1526+
uint32_t heap_snapshot_near_heap_limit_ = 0;
1527+
bool heapsnapshot_near_heap_limit_callback_added_ = false;
15201528

15211529
uint32_t module_id_counter_ = 0;
15221530
uint32_t script_id_counter_ = 0;

src/node.cc

+2-3
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,8 @@ static void AtomicsWaitCallback(Isolate::AtomicsWaitEvent event,
282282
void Environment::InitializeDiagnostics() {
283283
isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
284284
Environment::BuildEmbedderGraph, this);
285-
if (options_->heap_snapshot_near_heap_limit > 0) {
286-
isolate_->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback,
287-
this);
285+
if (heap_snapshot_near_heap_limit_ > 0) {
286+
AddHeapSnapshotNearHeapLimitCallback();
288287
}
289288
if (options_->trace_uncaught)
290289
isolate_->SetCaptureStackTraceForUncaughtExceptions(true);

src/node_v8.cc

+12
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ void CachedDataVersionTag(const FunctionCallbackInfo<Value>& args) {
155155
args.GetReturnValue().Set(result);
156156
}
157157

158+
void SetHeapSnapshotNearHeapLimit(const FunctionCallbackInfo<Value>& args) {
159+
CHECK(args[0]->IsUint32());
160+
Environment* env = Environment::GetCurrent(args);
161+
uint32_t limit = args[0].As<v8::Uint32>()->Value();
162+
CHECK_GT(limit, 0);
163+
env->AddHeapSnapshotNearHeapLimitCallback();
164+
env->set_heap_snapshot_near_heap_limit(limit);
165+
}
166+
158167
void UpdateHeapStatisticsBuffer(const FunctionCallbackInfo<Value>& args) {
159168
BindingData* data = Environment::GetBindingData<BindingData>(args);
160169
HeapStatistics s;
@@ -212,6 +221,8 @@ void Initialize(Local<Object> target,
212221
CachedDataVersionTag);
213222
env->SetMethod(
214223
target, "updateHeapStatisticsBuffer", UpdateHeapStatisticsBuffer);
224+
env->SetMethodNoSideEffect(target, "setHeapSnapshotNearHeapLimit",
225+
SetHeapSnapshotNearHeapLimit);
215226

216227
env->SetMethod(
217228
target, "updateHeapCodeStatisticsBuffer", UpdateHeapCodeStatisticsBuffer);
@@ -259,6 +270,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
259270
registry->Register(UpdateHeapCodeStatisticsBuffer);
260271
registry->Register(UpdateHeapSpaceStatisticsBuffer);
261272
registry->Register(SetFlagsFromString);
273+
registry->Register(SetHeapSnapshotNearHeapLimit);
262274
}
263275

264276
} // namespace v8_utils
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
const path = require('path');
3+
const v8 = require('v8');
4+
5+
v8.setHeapSnapshotNearHeapLimit(+process.env.limit);
6+
if (process.env.limit2) {
7+
v8.setHeapSnapshotNearHeapLimit(+process.env.limit2);
8+
}
9+
require(path.resolve(__dirname, 'grow.js'));
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+
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+
}

0 commit comments

Comments
 (0)