Skip to content

Commit

Permalink
feat: use 'expose-gc/function' to get the Node.js garbage collector
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed May 29, 2021
1 parent fbe665b commit 9879bcc
Show file tree
Hide file tree
Showing 22 changed files with 106 additions and 109 deletions.
4 changes: 1 addition & 3 deletions packages/SwingSet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@types/tmp": "^0.2.0",
"anylogger": "^0.21.0",
"esm": "^3.2.5",
"expose-gc": "^1.0.0",
"node-lmdb": "^0.9.4",
"re2": "^1.10.5",
"semver": "^6.3.0",
Expand Down Expand Up @@ -87,9 +88,6 @@
"files": [
"test/**/test-*.js"
],
"nodeArguments": [
"--expose-gc"
],
"require": [
"esm"
],
Expand Down
7 changes: 4 additions & 3 deletions packages/SwingSet/src/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { type as osType } from 'os';
import { Worker } from 'worker_threads';
import anylogger from 'anylogger';
import { tmpName } from 'tmp';
import engineGC from 'expose-gc/function';

import { assert, details as X } from '@agoric/assert';
import { isTamed, tameMetering } from '@agoric/tame-metering';
Expand All @@ -20,7 +21,7 @@ import { xsnap, makeSnapstore } from '@agoric/xsnap';
import { WeakRef, FinalizationRegistry } from './weakref';
import { startSubprocessWorker } from './spawnSubprocessWorker';
import { waitUntilQuiescent } from './waitUntilQuiescent';
import { gcAndFinalize } from './gc-and-finalize';
import { makeGcAndFinalize } from './gc-and-finalize';
import { insistStorageAPI } from './storageAPI';
import { insistCapData } from './capdata';
import { parseVatSlot } from './parseVatSlots';
Expand Down Expand Up @@ -272,7 +273,7 @@ export async function makeSwingsetController(
const supercode = require.resolve(
'./kernel/vatManager/supervisor-subprocess-node.js',
);
const args = ['--expose-gc', '-r', 'esm', supercode];
const args = ['-r', 'esm', supercode];
return startSubprocessWorker(process.execPath, args);
}

Expand All @@ -299,7 +300,7 @@ export async function makeSwingsetController(
writeSlogObject,
WeakRef,
FinalizationRegistry,
gcAndFinalize,
gcAndFinalize: makeGcAndFinalize(engineGC),
};

const kernelOptions = { verbose };
Expand Down
43 changes: 22 additions & 21 deletions packages/SwingSet/src/gc-and-finalize.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global gc setImmediate */
/* global setImmediate */

/* A note on our GC terminology:
*
Expand Down Expand Up @@ -33,9 +33,10 @@
* The transition from UNREACHABLE to COLLECTED can happen spontaneously, as
* the JS engine decides it wants to perform GC. It will also happen
* deliberately if we provoke a GC call with a magic function like `gc()`
* (when Node.js is run with `--expose-gc`, or when XS is configured to
* provide it as a C-level callback). We can force GC, but we cannot prevent
* it from happening at other times.
* (when Node.js imports `expose-gc/function`, which is morally-equivalent to
* running with `--expose-gc`, or when XS is configured to provide it as a
* C-level callback). We can force GC, but we cannot prevent it from happening
* at other times.
*
* FinalizationRegistry callbacks are defined to run on their own turn, so
* the transition from COLLECTED to FINALIZED occurs at a turn boundary.
Expand Down Expand Up @@ -63,23 +64,23 @@
* dummy pre-resolved Promise.
*/

let alreadyWarned = false;
export async function gcAndFinalize() {
if (typeof gc !== 'function') {
if (!alreadyWarned) {
alreadyWarned = true;
console.warn(
Error(`no gc() function; disabling deterministic finalizers`),
);
}
return;
export function makeGcAndFinalize(gcPower) {
if (typeof gcPower !== 'function') {
console.warn(
Error(`no gcPower() function; skipping finalizer provocation`),
);
}
return async function gcAndFinalize() {
if (typeof gcPower !== 'function') {
return;
}

// on Node.js, GC seems to work better if the promise queue is empty first
await new Promise(setImmediate);
// on xsnap, we must do it twice for some reason
await new Promise(setImmediate);
gc();
// this gives finalizers a chance to run
await new Promise(setImmediate);
// on Node.js, GC seems to work better if the promise queue is empty first
await new Promise(setImmediate);
// on xsnap, we must do it twice for some reason
await new Promise(setImmediate);
gcPower();
// this gives finalizers a chance to run
await new Promise(setImmediate);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import '@agoric/install-ses';
import { parentPort } from 'worker_threads';
import anylogger from 'anylogger';
import engineGC from 'expose-gc/function';

import '../../types';
import { assert, details as X } from '@agoric/assert';
import { importBundle } from '@agoric/import-bundle';
import { makeMarshal } from '@agoric/marshal';
import { WeakRef, FinalizationRegistry } from '../../weakref';
import { gcAndFinalize } from '../../gc-and-finalize';
import { makeGcAndFinalize } from '../../gc-and-finalize';
import { waitUntilQuiescent } from '../../waitUntilQuiescent';
import { makeLiveSlots } from '../liveSlots';
import {
Expand Down Expand Up @@ -79,7 +80,7 @@ parentPort.on('message', ([type, ...margs]) => {
WeakRef,
FinalizationRegistry,
waitUntilQuiescent,
gcAndFinalize,
gcAndFinalize: makeGcAndFinalize(engineGC),
});
const ls = makeLiveSlots(
syscall,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import '@agoric/install-ses';

import anylogger from 'anylogger';
import fs from 'fs';
import engineGC from 'expose-gc/function';

import { assert, details as X } from '@agoric/assert';
import { importBundle } from '@agoric/import-bundle';
import { makeMarshal } from '@agoric/marshal';
import { WeakRef, FinalizationRegistry } from '../../weakref';
import { gcAndFinalize } from '../../gc-and-finalize';
import { makeGcAndFinalize } from '../../gc-and-finalize';
import { arrayEncoderStream, arrayDecoderStream } from '../../worker-protocol';
import {
netstringEncoderStream,
Expand Down Expand Up @@ -92,7 +93,7 @@ fromParent.on('data', ([type, ...margs]) => {
WeakRef,
FinalizationRegistry,
waitUntilQuiescent,
gcAndFinalize,
gcAndFinalize: makeGcAndFinalize(engineGC),
});
const ls = makeLiveSlots(
syscall,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { makeMarshal } from '@agoric/marshal';
import '../../types';
// grumble... waitUntilQuiescent is exported and closes over ambient authority
import { waitUntilQuiescent } from '../../waitUntilQuiescent';
import { gcAndFinalize } from '../../gc-and-finalize';
import { makeGcAndFinalize } from '../../gc-and-finalize';
import { insistVatDeliveryObject, insistVatSyscallResult } from '../../message';

import { makeLiveSlots } from '../liveSlots';
Expand Down Expand Up @@ -185,7 +185,7 @@ function makeWorker(port) {
WeakRef,
FinalizationRegistry,
waitUntilQuiescent,
gcAndFinalize,
gcAndFinalize: makeGcAndFinalize(globalThis.gc),
});

const ls = makeLiveSlots(
Expand Down
6 changes: 4 additions & 2 deletions packages/SwingSet/test/liveslots-helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import engineGC from 'expose-gc/function';

import { WeakRef, FinalizationRegistry } from '../src/weakref';
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
import { gcAndFinalize } from '../src/gc-and-finalize';
import { makeGcAndFinalize } from '../src/gc-and-finalize';
import { makeLiveSlots } from '../src/kernel/liveSlots';

export function buildSyscall() {
Expand Down Expand Up @@ -43,7 +45,7 @@ export function makeDispatch(
WeakRef,
FinalizationRegistry,
waitUntilQuiescent,
gcAndFinalize,
gcAndFinalize: makeGcAndFinalize(engineGC),
});
const { setBuildRootObject, dispatch } = makeLiveSlots(
syscall,
Expand Down
22 changes: 9 additions & 13 deletions packages/SwingSet/test/test-gc-and-finalize.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
/* global gc FinalizationRegistry WeakRef */
/* global FinalizationRegistry WeakRef */
// eslint-disable-next-line import/order
import { test } from '../tools/prepare-test-env-ava';

import engineGC from 'expose-gc/function';

import * as childProcess from 'child_process';
import * as os from 'os';
import { xsnap } from '@agoric/xsnap';
import { gcAndFinalize } from '../src/gc-and-finalize';

test(`have gc() on Node.js`, async t => {
t.is(typeof gc, 'function', 'environment is missing top-level gc()');
// Under Node.js, you must use `node --expose-gc PROGRAM`. Under AVA+Node,
// add `nodeArguments: [ "--expose-gc" ]` to the package.json 'ava:'
// stanza. Under XS, make sure your application (e.g. xsnap) provides a
// `gc` C callback on the global object.
});
import { makeGcAndFinalize } from '../src/gc-and-finalize';

function setup() {
const victim = { doomed: 'oh no' };
Expand All @@ -22,8 +16,9 @@ function setup() {
finalized[0] = 'finalizer was called';
});
const wr = new WeakRef(victim);
const gcAndFinalize = makeGcAndFinalize(engineGC);
fr.register(victim, 'tag');
return { finalized, fr, wr };
return { finalized, fr, wr, gcAndFinalize };
}

async function provokeGC() {
Expand All @@ -32,7 +27,7 @@ async function provokeGC() {

// we must retain the FinalizationRegistry to let the callback fire
// eslint-disable-next-line no-unused-vars
const { finalized, fr, wr } = setup();
const { finalized, fr, wr, gcAndFinalize } = setup();

// the transition from UNREACHABLE to COLLECTED can happen at any moment,
// but is far more likely to happen if we force it
Expand Down Expand Up @@ -81,7 +76,8 @@ test(`can provoke gc on xsnap`, async t => {
const opts = options();
const vat = xsnap(opts);
const code = `
${gcAndFinalize}
${makeGcAndFinalize}
const engineGC = globalThis.gc;
${setup}
${provokeGC}
provokeGC().then(data => issueCommand(ArrayBuffer.fromString(JSON.stringify(data))));
Expand Down
5 changes: 4 additions & 1 deletion packages/SwingSet/test/test-liveslots.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* global WeakRef */
// eslint-disable-next-line import/order
import { test } from '../tools/prepare-test-env-ava';
import engineGC from 'expose-gc/function';

import { E } from '@agoric/eventual-send';
import { Far } from '@agoric/marshal';
import { makePromiseKit } from '@agoric/promise-kit';
import { assert, details as X } from '@agoric/assert';
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
import { gcAndFinalize } from '../src/gc-and-finalize';
import { makeGcAndFinalize } from '../src/gc-and-finalize';
import { makeLiveSlots } from '../src/kernel/liveSlots';
import { buildSyscall, makeDispatch } from './liveslots-helpers';
import {
Expand Down Expand Up @@ -323,6 +324,7 @@ test('liveslots retires outbound promise IDs after reject', async t => {
});

test('liveslots retains pending exported promise', async t => {
const gcAndFinalize = makeGcAndFinalize(engineGC);
const { log, syscall } = buildSyscall();
let watch;
const success = [];
Expand Down Expand Up @@ -661,6 +663,7 @@ test('disavow', async t => {
});

test('liveslots retains device nodes', async t => {
const gcAndFinalize = makeGcAndFinalize(engineGC);
const { syscall } = buildSyscall();
let watch;
const recognize = new WeakSet(); // real WeakSet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* global WeakRef */
// eslint-disable-next-line import/order
import { test } from '../../tools/prepare-test-env-ava';

// eslint-disable-next-line import/order
import { Far } from '@agoric/marshal';
import engineGC from 'expose-gc/function';

import { gcAndFinalize } from '../../src/gc-and-finalize';
import { makeGcAndFinalize } from '../../src/gc-and-finalize';
import { makeFakeVirtualObjectManager } from '../../tools/fakeVirtualObjectManager';

// empty object, used as makeWeakStore() key
Expand Down Expand Up @@ -76,6 +77,7 @@ function stashRemotableFour(holderMaker) {
}

test('remotables retained by virtualized data', async t => {
const gcAndFinalize = makeGcAndFinalize(engineGC);
const vomOptions = { cacheSize: 3, weak: true };
const vom = makeFakeVirtualObjectManager(vomOptions);
const { makeWeakStore, makeKind } = vom;
Expand Down
18 changes: 4 additions & 14 deletions packages/SwingSet/test/virtualObjects/test-weakcollections.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* global __dirname globalThis */
/* global __dirname */
// eslint-disable-next-line import/order
import { test } from '../../tools/prepare-test-env-ava';

// eslint-disable-next-line import/order
import engineGC from 'expose-gc/function';
import path from 'path';

import { provideHostStorage } from '../../src/hostStorage';
Expand All @@ -17,17 +18,7 @@ function capargs(args, slots = []) {
return capdata(JSON.stringify(args), slots);
}

let gc;
function insistGC(t) {
if (globalThis.gc) {
gc = globalThis.gc;
} else {
t.fail(`GC needs to be enabled for this test to work`);
}
}

test('weakMap in vat', async t => {
insistGC(t);
const config = {
bootstrap: 'bootstrap',
defaultManagerType: 'local',
Expand Down Expand Up @@ -70,7 +61,7 @@ test('weakMap in vat', async t => {
'probe of [object Promise] returns fep',
]);
await doSimple('betweenProbes');
gc();
engineGC();
const postGCResult = await doSimple('runProbes');
t.deepEqual(postGCResult, capargs('probes done'));
t.deepEqual(nextLog(), [
Expand All @@ -86,7 +77,6 @@ test('weakMap in vat', async t => {
});

test('weakMap vref handling', async t => {
insistGC(t);
const log = [];
const {
VirtualObjectAwareWeakMap,
Expand Down
7 changes: 1 addition & 6 deletions packages/agoric-cli/lib/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,7 @@ export default async function startMain(progname, rawArgs, powers, opts) {
const SDK_IMAGE = `agoric/agoric-sdk:${opts.dockerTag}`;
const SOLO_IMAGE = `agoric/cosmic-swingset-solo:${opts.dockerTag}`;

const pspawnEnv = {
...process.env,
// TODO: We'd love to --expose-gc in this environment variable,
// but Node.js rejects it.
// NODE_OPTIONS: `${process.env.NODE_OPTIONS || ''} --expose-gc`,
};
const pspawnEnv = { ...process.env };
const pspawn = makePspawn({ env: pspawnEnv, spawn, log, chalk });

let keysSpawn;
Expand Down
1 change: 0 additions & 1 deletion packages/dapp-svelte-wallet/api/src/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ export function buildRootObject(_vatPowers) {
harden(preapprovedBridge);

async function getWallet(bank) {
console.error('/// importing bank assets', bank);
if (bank) {
walletRoot.importBankAssets(bank);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/solo/test/test-home.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ test.serial('home.localTimerService makeRepeater', async t => {
const notifier = E(localTimerService).makeNotifier(1, 1);
await E(notifier).getUpdateSince();

t.is(1, handler.getCalls());
t.truthy(handler.getCalls() >= 1);
t.truthy(handler.getArgs()[0] > timestamp);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/swingset-runner/autobench
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ for (const suite of SUITES) {
}
}
spawnNodeSync([
'--expose-gc', '-r', 'esm', 'bin/runner', '--init',
'-r', 'esm', 'bin/runner', '--init',
'--benchmark', brounds, '--statsfile', benchstats, ...configFlags,
'run', `demo/${suite}`, '--quiet', '--prime',
]);
Expand Down
Loading

0 comments on commit 9879bcc

Please sign in to comment.