mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
v8: add setHeapSnapshotNearHeapLimit
PR-URL: https://github.com/nodejs/node/pull/44420 Refs: https://github.com/nodejs/node/pull/33010 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
0b5b5edd86
commit
e62f6ce630
@ -356,6 +356,20 @@ if (isMainThread) {
|
||||
}
|
||||
```
|
||||
|
||||
## `v8.setHeapSnapshotNearHeapLimit(limit)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1 - Experimental
|
||||
|
||||
* `limit` {integer}
|
||||
|
||||
The API is a no-op if `--heapsnapshot-near-heap-limit` is already set from the
|
||||
command line or the API is called more than once. `limit` must be a positive
|
||||
integer. See [`--heapsnapshot-near-heap-limit`][] for more information.
|
||||
|
||||
## Serialization API
|
||||
|
||||
The serialization API provides means of serializing JavaScript values in a way
|
||||
@ -1020,6 +1034,7 @@ Returns true if the Node.js instance is run to build a snapshot.
|
||||
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||
[Hook Callbacks]: #hook-callbacks
|
||||
[V8]: https://developers.google.com/v8/
|
||||
[`--heapsnapshot-near-heap-limit`]: cli.md#--heapsnapshot-near-heap-limitmax_count
|
||||
[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
|
||||
[`Buffer`]: buffer.md
|
||||
[`DefaultDeserializer`]: #class-v8defaultdeserializer
|
||||
|
19
lib/v8.js
19
lib/v8.js
@ -33,7 +33,7 @@ const {
|
||||
} = primordials;
|
||||
|
||||
const { Buffer } = require('buffer');
|
||||
const { validateString } = require('internal/validators');
|
||||
const { validateString, validateUint32 } = require('internal/validators');
|
||||
const {
|
||||
Serializer,
|
||||
Deserializer
|
||||
@ -59,6 +59,7 @@ const {
|
||||
} = internalBinding('heap_utils');
|
||||
const { HeapSnapshotStream } = require('internal/heap_utils');
|
||||
const promiseHooks = require('internal/promise_hooks');
|
||||
const { getOptionValue } = require('internal/options');
|
||||
|
||||
/**
|
||||
* Generates a snapshot of the current V8 heap
|
||||
@ -95,6 +96,7 @@ const {
|
||||
updateHeapStatisticsBuffer,
|
||||
updateHeapSpaceStatisticsBuffer,
|
||||
updateHeapCodeStatisticsBuffer,
|
||||
setHeapSnapshotNearHeapLimit: _setHeapSnapshotNearHeapLimit,
|
||||
|
||||
// Properties for heap statistics buffer extraction.
|
||||
kTotalHeapSizeIndex,
|
||||
@ -226,6 +228,18 @@ function getHeapCodeStatistics() {
|
||||
};
|
||||
}
|
||||
|
||||
let heapSnapshotNearHeapLimitCallbackAdded = false;
|
||||
function setHeapSnapshotNearHeapLimit(limit) {
|
||||
validateUint32(limit, 'limit', 1);
|
||||
if (heapSnapshotNearHeapLimitCallbackAdded ||
|
||||
getOptionValue('--heapsnapshot-near-heap-limit') > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
heapSnapshotNearHeapLimitCallbackAdded = true;
|
||||
_setHeapSnapshotNearHeapLimit(limit);
|
||||
}
|
||||
|
||||
/* V8 serialization API */
|
||||
|
||||
/* JS methods for the base objects */
|
||||
@ -387,5 +401,6 @@ module.exports = {
|
||||
serialize,
|
||||
writeHeapSnapshot,
|
||||
promiseHooks,
|
||||
startupSnapshot
|
||||
startupSnapshot,
|
||||
setHeapSnapshotNearHeapLimit,
|
||||
};
|
||||
|
@ -892,6 +892,24 @@ Realm* Environment::principal_realm() const {
|
||||
return principal_realm_.get();
|
||||
}
|
||||
|
||||
inline void Environment::set_heap_snapshot_near_heap_limit(uint32_t limit) {
|
||||
heap_snapshot_near_heap_limit_ = limit;
|
||||
}
|
||||
|
||||
inline void Environment::AddHeapSnapshotNearHeapLimitCallback() {
|
||||
DCHECK(!heapsnapshot_near_heap_limit_callback_added_);
|
||||
heapsnapshot_near_heap_limit_callback_added_ = true;
|
||||
isolate_->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback, this);
|
||||
}
|
||||
|
||||
inline void Environment::RemoveHeapSnapshotNearHeapLimitCallback(
|
||||
size_t heap_limit) {
|
||||
DCHECK(heapsnapshot_near_heap_limit_callback_added_);
|
||||
heapsnapshot_near_heap_limit_callback_added_ = false;
|
||||
isolate_->RemoveNearHeapLimitCallback(Environment::NearHeapLimitCallback,
|
||||
heap_limit);
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
|
||||
// These two files depend on each other. Including base_object-inl.h after this
|
||||
|
19
src/env.cc
19
src/env.cc
@ -682,6 +682,9 @@ Environment::Environment(IsolateData* isolate_data,
|
||||
inspector_host_port_ = std::make_shared<ExclusiveAccess<HostPort>>(
|
||||
options_->debug_options().host_port);
|
||||
|
||||
heap_snapshot_near_heap_limit_ =
|
||||
static_cast<uint32_t>(options_->heap_snapshot_near_heap_limit);
|
||||
|
||||
if (!(flags_ & EnvironmentFlags::kOwnsProcessState)) {
|
||||
set_abort_on_uncaught_exception(false);
|
||||
}
|
||||
@ -797,9 +800,8 @@ Environment::~Environment() {
|
||||
// FreeEnvironment() should have set this.
|
||||
CHECK(is_stopping());
|
||||
|
||||
if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) {
|
||||
isolate_->RemoveNearHeapLimitCallback(Environment::NearHeapLimitCallback,
|
||||
0);
|
||||
if (heapsnapshot_near_heap_limit_callback_added_) {
|
||||
RemoveHeapSnapshotNearHeapLimitCallback(0);
|
||||
}
|
||||
|
||||
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
|
||||
@ -1788,8 +1790,7 @@ size_t Environment::NearHeapLimitCallback(void* data,
|
||||
Debug(env,
|
||||
DebugCategory::DIAGNOSTICS,
|
||||
"Not generating snapshots because it's too risky.\n");
|
||||
env->isolate()->RemoveNearHeapLimitCallback(NearHeapLimitCallback,
|
||||
initial_heap_limit);
|
||||
env->RemoveHeapSnapshotNearHeapLimitCallback(initial_heap_limit);
|
||||
// The new limit must be higher than current_heap_limit or V8 might
|
||||
// crash.
|
||||
return current_heap_limit + 1;
|
||||
@ -1809,17 +1810,15 @@ size_t Environment::NearHeapLimitCallback(void* data,
|
||||
|
||||
// Remove the callback first in case it's triggered when generating
|
||||
// the snapshot.
|
||||
env->isolate()->RemoveNearHeapLimitCallback(NearHeapLimitCallback,
|
||||
initial_heap_limit);
|
||||
env->RemoveHeapSnapshotNearHeapLimitCallback(initial_heap_limit);
|
||||
|
||||
heap::WriteSnapshot(env, filename.c_str());
|
||||
env->heap_limit_snapshot_taken_ += 1;
|
||||
|
||||
// Don't take more snapshots than the number specified by
|
||||
// --heapsnapshot-near-heap-limit.
|
||||
if (env->heap_limit_snapshot_taken_ <
|
||||
env->options_->heap_snapshot_near_heap_limit) {
|
||||
env->isolate()->AddNearHeapLimitCallback(NearHeapLimitCallback, env);
|
||||
if (env->heap_limit_snapshot_taken_ < env->heap_snapshot_near_heap_limit_) {
|
||||
env->AddHeapSnapshotNearHeapLimitCallback();
|
||||
}
|
||||
|
||||
FPrintF(stderr, "Wrote snapshot to %s\n", filename.c_str());
|
||||
|
10
src/env.h
10
src/env.h
@ -1040,6 +1040,12 @@ class Environment : public MemoryRetainer {
|
||||
template <typename T>
|
||||
void ForEachBaseObject(T&& iterator);
|
||||
|
||||
inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
|
||||
|
||||
inline void AddHeapSnapshotNearHeapLimitCallback();
|
||||
|
||||
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
|
||||
|
||||
private:
|
||||
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
|
||||
const char* errmsg);
|
||||
@ -1097,7 +1103,9 @@ class Environment : public MemoryRetainer {
|
||||
std::string exec_path_;
|
||||
|
||||
bool is_processing_heap_limit_callback_ = false;
|
||||
int64_t heap_limit_snapshot_taken_ = 0;
|
||||
uint32_t heap_limit_snapshot_taken_ = 0;
|
||||
uint32_t heap_snapshot_near_heap_limit_ = 0;
|
||||
bool heapsnapshot_near_heap_limit_callback_added_ = false;
|
||||
|
||||
uint32_t module_id_counter_ = 0;
|
||||
uint32_t script_id_counter_ = 0;
|
||||
|
@ -241,9 +241,8 @@ static void AtomicsWaitCallback(Isolate::AtomicsWaitEvent event,
|
||||
void Environment::InitializeDiagnostics() {
|
||||
isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
|
||||
Environment::BuildEmbedderGraph, this);
|
||||
if (options_->heap_snapshot_near_heap_limit > 0) {
|
||||
isolate_->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback,
|
||||
this);
|
||||
if (heap_snapshot_near_heap_limit_ > 0) {
|
||||
AddHeapSnapshotNearHeapLimitCallback();
|
||||
}
|
||||
if (options_->trace_uncaught)
|
||||
isolate_->SetCaptureStackTraceForUncaughtExceptions(true);
|
||||
|
@ -157,6 +157,15 @@ void CachedDataVersionTag(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void SetHeapSnapshotNearHeapLimit(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args[0]->IsUint32());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
uint32_t limit = args[0].As<v8::Uint32>()->Value();
|
||||
CHECK_GT(limit, 0);
|
||||
env->AddHeapSnapshotNearHeapLimitCallback();
|
||||
env->set_heap_snapshot_near_heap_limit(limit);
|
||||
}
|
||||
|
||||
void UpdateHeapStatisticsBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
BindingData* data = Environment::GetBindingData<BindingData>(args);
|
||||
HeapStatistics s;
|
||||
@ -212,6 +221,10 @@ void Initialize(Local<Object> target,
|
||||
|
||||
SetMethodNoSideEffect(
|
||||
context, target, "cachedDataVersionTag", CachedDataVersionTag);
|
||||
SetMethodNoSideEffect(context,
|
||||
target,
|
||||
"setHeapSnapshotNearHeapLimit",
|
||||
SetHeapSnapshotNearHeapLimit);
|
||||
SetMethod(context,
|
||||
target,
|
||||
"updateHeapStatisticsBuffer",
|
||||
@ -267,6 +280,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(UpdateHeapCodeStatisticsBuffer);
|
||||
registry->Register(UpdateHeapSpaceStatisticsBuffer);
|
||||
registry->Register(SetFlagsFromString);
|
||||
registry->Register(SetHeapSnapshotNearHeapLimit);
|
||||
}
|
||||
|
||||
} // namespace v8_utils
|
||||
|
9
test/fixtures/workload/grow-and-set-near-heap-limit.js
vendored
Normal file
9
test/fixtures/workload/grow-and-set-near-heap-limit.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const v8 = require('v8');
|
||||
|
||||
v8.setHeapSnapshotNearHeapLimit(+process.env.limit);
|
||||
if (process.env.limit2) {
|
||||
v8.setHeapSnapshotNearHeapLimit(+process.env.limit2);
|
||||
}
|
||||
require(path.resolve(__dirname, 'grow.js'));
|
15
test/fixtures/workload/grow-worker-and-set-near-heap-limit.js
vendored
Normal file
15
test/fixtures/workload/grow-worker-and-set-near-heap-limit.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const { Worker } = require('worker_threads');
|
||||
const max_snapshots = parseInt(process.env.TEST_SNAPSHOTS) || 1;
|
||||
new Worker(path.join(__dirname, 'grow-and-set-near-heap-limit.js'), {
|
||||
env: {
|
||||
...process.env,
|
||||
limit: max_snapshots,
|
||||
},
|
||||
resourceLimits: {
|
||||
maxOldGenerationSizeMb:
|
||||
parseInt(process.env.TEST_OLD_SPACE_SIZE) || 20
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,41 @@
|
||||
// Copy from test-heapsnapshot-near-heap-limit-worker.js
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const assert = require('assert');
|
||||
const { spawnSync } = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const fs = require('fs');
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
NODE_DEBUG_NATIVE: 'diagnostics'
|
||||
};
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const child = spawnSync(process.execPath, [
|
||||
fixtures.path('workload', 'grow-worker-and-set-near-heap-limit.js'),
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
env: {
|
||||
TEST_SNAPSHOTS: 1,
|
||||
TEST_OLD_SPACE_SIZE: 50,
|
||||
...env
|
||||
}
|
||||
});
|
||||
console.log(child.stdout.toString());
|
||||
const stderr = child.stderr.toString();
|
||||
console.log(stderr);
|
||||
const risky = /Not generating snapshots because it's too risky/.test(stderr);
|
||||
if (!risky) {
|
||||
// There should be one snapshot taken and then after the
|
||||
// snapshot heap limit callback is popped, the OOM callback
|
||||
// becomes effective.
|
||||
assert(stderr.includes('ERR_WORKER_OUT_OF_MEMORY'));
|
||||
const list = fs.readdirSync(tmpdir.path)
|
||||
.filter((file) => file.endsWith('.heapsnapshot'));
|
||||
assert.strictEqual(list.length, 1);
|
||||
}
|
||||
}
|
144
test/pummel/test-heapsnapshot-near-heap-limit-by-api.js
Normal file
144
test/pummel/test-heapsnapshot-near-heap-limit-by-api.js
Normal file
@ -0,0 +1,144 @@
|
||||
// Copy from test-heapsnapshot-near-heap-limit.js
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (common.isPi) {
|
||||
common.skip('Too slow for Raspberry Pi devices');
|
||||
}
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const assert = require('assert');
|
||||
const { spawnSync } = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const fs = require('fs');
|
||||
const v8 = require('v8');
|
||||
|
||||
const invalidValues = [-1, '', {}, NaN, undefined];
|
||||
for (let i = 0; i < invalidValues.length; i++) {
|
||||
assert.throws(() => v8.setHeapSnapshotNearHeapLimit(invalidValues[i]),
|
||||
/ERR_INVALID_ARG_TYPE|ERR_OUT_OF_RANGE/);
|
||||
}
|
||||
|
||||
// Set twice
|
||||
v8.setHeapSnapshotNearHeapLimit(1);
|
||||
v8.setHeapSnapshotNearHeapLimit(2);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
NODE_DEBUG_NATIVE: 'diagnostics',
|
||||
};
|
||||
|
||||
{
|
||||
console.log('\nTesting set by cmd option and api');
|
||||
tmpdir.refresh();
|
||||
const child = spawnSync(process.execPath, [
|
||||
'--trace-gc',
|
||||
'--heapsnapshot-near-heap-limit=1',
|
||||
'--max-old-space-size=50',
|
||||
fixtures.path('workload', 'grow-and-set-near-heap-limit.js'),
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
env: {
|
||||
...env,
|
||||
limit: 2,
|
||||
},
|
||||
});
|
||||
console.log(child.stdout.toString());
|
||||
const stderr = child.stderr.toString();
|
||||
console.log(stderr);
|
||||
assert(common.nodeProcessAborted(child.status, child.signal),
|
||||
'process should have aborted, but did not');
|
||||
const list = fs.readdirSync(tmpdir.path)
|
||||
.filter((file) => file.endsWith('.heapsnapshot'));
|
||||
const risky = [...stderr.matchAll(
|
||||
/Not generating snapshots because it's too risky/g)].length;
|
||||
assert(list.length + risky > 0 && list.length <= 1,
|
||||
`Generated ${list.length} snapshots ` +
|
||||
`and ${risky} was too risky`);
|
||||
}
|
||||
|
||||
{
|
||||
console.log('\nTesting limit = 1');
|
||||
tmpdir.refresh();
|
||||
const child = spawnSync(process.execPath, [
|
||||
'--trace-gc',
|
||||
'--max-old-space-size=50',
|
||||
fixtures.path('workload', 'grow-and-set-near-heap-limit.js'),
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
env: {
|
||||
...env,
|
||||
limit: 1,
|
||||
},
|
||||
});
|
||||
console.log(child.stdout.toString());
|
||||
const stderr = child.stderr.toString();
|
||||
console.log(stderr);
|
||||
assert(common.nodeProcessAborted(child.status, child.signal),
|
||||
'process should have aborted, but did not');
|
||||
const list = fs.readdirSync(tmpdir.path)
|
||||
.filter((file) => file.endsWith('.heapsnapshot'));
|
||||
const risky = [...stderr.matchAll(
|
||||
/Not generating snapshots because it's too risky/g)].length;
|
||||
assert(list.length + risky > 0 && list.length <= 1,
|
||||
`Generated ${list.length} snapshots ` +
|
||||
`and ${risky} was too risky`);
|
||||
}
|
||||
|
||||
{
|
||||
console.log('\nTesting set limit twice');
|
||||
tmpdir.refresh();
|
||||
const child = spawnSync(process.execPath, [
|
||||
'--trace-gc',
|
||||
'--max-old-space-size=50',
|
||||
fixtures.path('workload', 'grow-and-set-near-heap-limit.js'),
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
env: {
|
||||
...env,
|
||||
limit: 1,
|
||||
limit2: 2
|
||||
},
|
||||
});
|
||||
console.log(child.stdout.toString());
|
||||
const stderr = child.stderr.toString();
|
||||
console.log(stderr);
|
||||
assert(common.nodeProcessAborted(child.status, child.signal),
|
||||
'process should have aborted, but did not');
|
||||
const list = fs.readdirSync(tmpdir.path)
|
||||
.filter((file) => file.endsWith('.heapsnapshot'));
|
||||
const risky = [...stderr.matchAll(
|
||||
/Not generating snapshots because it's too risky/g)].length;
|
||||
assert(list.length + risky > 0 && list.length <= 1,
|
||||
`Generated ${list.length} snapshots ` +
|
||||
`and ${risky} was too risky`);
|
||||
}
|
||||
|
||||
{
|
||||
console.log('\nTesting limit = 3');
|
||||
tmpdir.refresh();
|
||||
const child = spawnSync(process.execPath, [
|
||||
'--trace-gc',
|
||||
'--max-old-space-size=50',
|
||||
fixtures.path('workload', 'grow-and-set-near-heap-limit.js'),
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
env: {
|
||||
...env,
|
||||
limit: 3,
|
||||
},
|
||||
});
|
||||
console.log(child.stdout.toString());
|
||||
const stderr = child.stderr.toString();
|
||||
console.log(stderr);
|
||||
assert(common.nodeProcessAborted(child.status, child.signal),
|
||||
'process should have aborted, but did not');
|
||||
const list = fs.readdirSync(tmpdir.path)
|
||||
.filter((file) => file.endsWith('.heapsnapshot'));
|
||||
const risky = [...stderr.matchAll(
|
||||
/Not generating snapshots because it's too risky/g)].length;
|
||||
assert(list.length + risky > 0 && list.length <= 3,
|
||||
`Generated ${list.length} snapshots ` +
|
||||
`and ${risky} was too risky`);
|
||||
}
|
@ -71,7 +71,7 @@ const env = {
|
||||
.filter((file) => file.endsWith('.heapsnapshot'));
|
||||
const risky = [...stderr.matchAll(
|
||||
/Not generating snapshots because it's too risky/g)].length;
|
||||
assert(list.length + risky > 0 && list.length <= 3,
|
||||
assert(list.length + risky > 0 && list.length <= 1,
|
||||
`Generated ${list.length} snapshots ` +
|
||||
`and ${risky} was too risky`);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user