Skip to content

Commit

Permalink
v8: add setHeapSnapshotNearHeapLimit
Browse files Browse the repository at this point in the history
  • Loading branch information
theanarkh committed Aug 27, 2022
1 parent ab89024 commit cde811c
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 4 deletions.
11 changes: 11 additions & 0 deletions doc/api/v8.md
Expand Up @@ -356,6 +356,16 @@ if (isMainThread) {
}
```

## `v8.setHeapSnapshotNearHeapLimit(limit)`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
See [`--heapsnapshot-near-heap-limit`][].

## Serialization API

The serialization API provides means of serializing JavaScript values in a way
Expand Down Expand Up @@ -1020,6 +1030,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
Expand Down
20 changes: 17 additions & 3 deletions lib/v8.js
Expand Up @@ -33,7 +33,7 @@ const {
} = primordials;

const { Buffer } = require('buffer');
const { validateString } = require('internal/validators');
const { validateString, validateNumber } = require('internal/validators');
const {
Serializer,
Deserializer
Expand All @@ -59,7 +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
* and writes it to a JSON file.
Expand Down Expand Up @@ -95,6 +95,7 @@ const {
updateHeapStatisticsBuffer,
updateHeapSpaceStatisticsBuffer,
updateHeapCodeStatisticsBuffer,
setHeapSnapshotNearHeapLimit: _setHeapSnapshotNearHeapLimit,

// Properties for heap statistics buffer extraction.
kTotalHeapSizeIndex,
Expand Down Expand Up @@ -226,6 +227,18 @@ function getHeapCodeStatistics() {
};
}

let called = false;
function setHeapSnapshotNearHeapLimit(limit) {
validateNumber(limit, 'limit', 1);
const value = getOptionValue('--heapsnapshot-near-heap-limit');
// Can not be called twice.
if (value > 0 || called) {
return;
}
called = true;
_setHeapSnapshotNearHeapLimit(limit);
}

/* V8 serialization API */

/* JS methods for the base objects */
Expand Down Expand Up @@ -387,5 +400,6 @@ module.exports = {
serialize,
writeHeapSnapshot,
promiseHooks,
startupSnapshot
startupSnapshot,
setHeapSnapshotNearHeapLimit,
};
18 changes: 18 additions & 0 deletions src/node_v8.cc
Expand Up @@ -157,6 +157,19 @@ void CachedDataVersionTag(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(result);
}

void SetHeapSnapshotNearHeapLimit(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsNumber());
Environment* env = Environment::GetCurrent(args);
if (env->options()->heap_snapshot_near_heap_limit == 0) {
Isolate* const isolate = args.GetIsolate();
int64_t limit = args[0].As<v8::Number>()->Value();
CHECK_GT(limit, 0);
env->options()->heap_snapshot_near_heap_limit = limit;
isolate->AddNearHeapLimitCallback(Environment::NearHeapLimitCallback,
env);
}
}

void UpdateHeapStatisticsBuffer(const FunctionCallbackInfo<Value>& args) {
BindingData* data = Environment::GetBindingData<BindingData>(args);
HeapStatistics s;
Expand Down Expand Up @@ -212,6 +225,10 @@ void Initialize(Local<Object> target,

SetMethodNoSideEffect(
context, target, "cachedDataVersionTag", CachedDataVersionTag);
SetMethodNoSideEffect(context,
target,
"setHeapSnapshotNearHeapLimit",
SetHeapSnapshotNearHeapLimit);
SetMethod(context,
target,
"updateHeapStatisticsBuffer",
Expand Down Expand Up @@ -267,6 +284,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(UpdateHeapCodeStatisticsBuffer);
registry->Register(UpdateHeapSpaceStatisticsBuffer);
registry->Register(SetFlagsFromString);
registry->Register(SetHeapSnapshotNearHeapLimit);
}

} // namespace v8_utils
Expand Down
@@ -0,0 +1,48 @@
// 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');

if (process.env.isChild === '1') {
require(fixtures.path('workload', 'grow-worker.js'));
const v8 = require('v8');
v8.setHeapSnapshotNearHeapLimit(1);
return;
}
const env = {
...process.env,
NODE_DEBUG_NATIVE: 'diagnostics'
};

{
tmpdir.refresh();
const child = spawnSync(process.execPath, [
__filename,
], {
cwd: tmpdir.path,
env: {
isChild: 1,
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);
}
}
131 changes: 131 additions & 0 deletions test/pummel/test-heapsnapshot-near-heap-limit-by-api.js
@@ -0,0 +1,131 @@
// 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');

if (process.env.isChild === '1') {
require(fixtures.path('workload', 'grow.js'));
const count = ~~process.env.count || 1;
for (let i = 0; i < count; i++) {
v8.setHeapSnapshotNearHeapLimit(+process.env.limit);
}
return;
}

const invalidValues = [-1, '', {}, NaN, undefined];
let errorCount = 0;
for (let i = 0; i < invalidValues.length; i++) {
try {
v8.setHeapSnapshotNearHeapLimit(invalidValues[i]);
} catch (e) {
console.log(e);
errorCount++;
}
}
assert.strictEqual(errorCount, invalidValues.length);

// Set twice
v8.setHeapSnapshotNearHeapLimit(1);
v8.setHeapSnapshotNearHeapLimit(2);

const env = {
...process.env,
NODE_DEBUG_NATIVE: 'diagnostics',
isChild: 1,
};

{
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.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 limit = 1');
tmpdir.refresh();
const child = spawnSync(process.execPath, [
'--trace-gc',
'--max-old-space-size=50',
__filename,
], {
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 limit = 3');
tmpdir.refresh();
const child = spawnSync(process.execPath, [
'--trace-gc',
'--max-old-space-size=50',
__filename,
], {
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`);
}
2 changes: 1 addition & 1 deletion test/pummel/test-heapsnapshot-near-heap-limit.js
Expand Up @@ -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`);
}
Expand Down

0 comments on commit cde811c

Please sign in to comment.