Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: provide GC tools (WeakRef/FinalizationRegistry) to makeLiveSlots #1924

Merged
merged 3 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/SwingSet/src/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { makeMeteringTransformer } from '@agoric/transform-metering';
import { makeTransform } from '@agoric/transform-eventual-send';
import { locateWorkerBin } from '@agoric/xs-vat-worker';

import { WeakRef, FinalizationRegistry } from './weakref';
import { startSubprocessWorker } from './spawnSubprocessWorker';
import { waitUntilQuiescent } from './waitUntilQuiescent';
import { insistStorageAPI } from './storageAPI';
Expand Down Expand Up @@ -183,6 +184,8 @@ export async function makeSwingsetController(
startSubprocessWorkerNode,
startSubprocessWorkerXS,
writeSlogObject,
WeakRef,
FinalizationRegistry,
};

const kernelOptions = { verbose };
Expand Down
4 changes: 4 additions & 0 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export default function buildKernel(
startSubprocessWorkerNode,
startSubprocessWorkerXS,
writeSlogObject,
WeakRef,
FinalizationRegistry,
} = kernelEndowments;
deviceEndowments = { ...deviceEndowments }; // copy so we can modify
const { verbose } = kernelOptions;
Expand Down Expand Up @@ -529,6 +531,7 @@ export default function buildKernel(
}
}

const gcTools = harden({ WeakRef, FinalizationRegistry });
const vatManagerFactory = makeVatManagerFactory({
allVatPowers,
kernelKeeper,
Expand All @@ -540,6 +543,7 @@ export default function buildKernel(
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
gcTools,
});

function buildVatSyscallHandler(vatID, translators) {
Expand Down
2 changes: 2 additions & 0 deletions packages/SwingSet/src/kernel/vatManager/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function makeVatManagerFactory({
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
gcTools,
}) {
const localFactory = makeLocalVatManagerFactory({
allVatPowers,
Expand All @@ -22,6 +23,7 @@ export function makeVatManagerFactory({
meterManager,
transformMetering,
waitUntilQuiescent,
gcTools,
});

const nodeWorkerFactory = makeNodeWorkerVatManagerFactory({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function makeLocalVatManagerFactory(tools) {
meterManager,
transformMetering,
waitUntilQuiescent,
gcTools,
} = tools;

const { makeGetMeter, refillAllMeters, stopGlobalMeter } = meterManager;
Expand Down Expand Up @@ -107,7 +108,7 @@ export function makeLocalVatManagerFactory(tools) {

// we might or might not use this, depending upon whether the vat exports
// 'buildRootObject' or a default 'setup' function
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters);
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters, gcTools);

let meterRecord = null;
if (metered) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import anylogger from 'anylogger';
import { assert } from '@agoric/assert';
import { importBundle } from '@agoric/import-bundle';
import { Remotable, getInterfaceOf, makeMarshal } from '@agoric/marshal';
import { WeakRef, FinalizationRegistry } from '../../weakref';
import { waitUntilQuiescent } from '../../waitUntilQuiescent';
import { makeLiveSlots } from '../liveSlots';

Expand Down Expand Up @@ -113,7 +114,8 @@ parentPort.on('message', ([type, ...margs]) => {
makeMarshal,
testLog,
};
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters);
const gcTools = harden({ WeakRef, FinalizationRegistry });
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters, gcTools);

const endowments = {
...ls.vatGlobals,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import fs from 'fs';
import { assert } from '@agoric/assert';
import { importBundle } from '@agoric/import-bundle';
import { Remotable, getInterfaceOf, makeMarshal } from '@agoric/marshal';
import { WeakRef, FinalizationRegistry } from '../../weakref';
import { arrayEncoderStream, arrayDecoderStream } from '../../worker-protocol';
import {
netstringEncoderStream,
Expand Down Expand Up @@ -133,7 +134,8 @@ fromParent.on('data', ([type, ...margs]) => {
makeMarshal,
testLog,
};
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters);
const gcTools = harden({ WeakRef, FinalizationRegistry });
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters, gcTools);

const endowments = {
...ls.vatGlobals,
Expand Down
121 changes: 121 additions & 0 deletions packages/SwingSet/src/weakref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* global globalThis */

import { assert, details as d } from '@agoric/assert';

const { defineProperties } = Object;

/*
* We retain a measure of compatibility with Node.js v12, which does not
* expose WeakRef or FinalizationRegistry (there is a --flag for it, but it's
* * not clear how stable it is). When running on a platform without these *
* tools, vats cannot do GC, and the tools they get will be no-ops. WeakRef
* instances will hold a strong reference, and the FinalizationRegistry will
* never invoke the callbacks.
*
* Modules should do:
*
* import { WeakRef, FinalizationRegistry } from '.../weakref';
*
*/

// TODO We need to migrate this into a ses-level tame-weakref.js, to happen
// as part of `lockdown`. In anticipation, this uses some of the patterns
// followed by the other tamings there.

// Emulate the internal [[WeakRefTarget]] slot. Despite the term "Weak" in the
// "WeakMap" used in the emulation, this map holds the target strongly. The only
// weakness here is that the weakref,target pair can go away together if the
// weakref is not reachable.
const weakRefTarget = new WeakMap();

const FakeWeakRef = function WeakRef(target) {
assert(
new.target !== undefined,
d`WeakRef Constructor requires 'new'`,
TypeError,
);
assert.equal(
Object(target),
target,
d`WeakRef target must be an object`,
TypeError,
);
weakRefTarget.set(this, target);
};

const InertWeakRef = function WeakRef(_target) {
throw new TypeError('Not available');
};

const FakeWeakRefPrototype = {
deref() {
return weakRefTarget.get(this);
},
[Symbol.toStringTag]: 'WeakRef',
};

defineProperties(FakeWeakRef, {
prototype: { value: FakeWeakRefPrototype },
});

const WeakRef = globalThis.WeakRef || FakeWeakRef;

// If there is a real WeakRef constructor, we still make it safe before
// exporting it. Unlike https://github.com/tc39/ecma262/issues/2214
// rather than deleting the `constructor` property, we follow the other
// taming patterns and point it at a throw-only inert one.
defineProperties(WeakRef.prototype, {
constructor: { value: InertWeakRef },
});

harden(WeakRef);

export { WeakRef };

// /////////////////////////////////////////////////////////////////////////////

const FakeFinalizationRegistry = function FinalizationRegistry(
cleanupCallback,
) {
assert(
new.target !== undefined,
d`FinalizationRegistry Constructor requires 'new'`,
TypeError,
);
assert.typeof(
cleanupCallback,
'function',
d`cleanupCallback must be a function`,
);
// fall off the end with an empty instance
};

const InertFinalizationRegistry = function FinalizationRegistry(
_cleanupCallback,
) {
throw new TypeError('Not available');
};

const FakeFinalizationRegistryPrototype = {
register() {},
unregister() {},
[Symbol.toStringTag]: 'FinalizationRegistry',
};

defineProperties(FakeFinalizationRegistry, {
prototype: { value: FakeFinalizationRegistryPrototype },
});

const FinalizationRegistry =
globalThis.FinalizationRegistry || FakeFinalizationRegistry;

// If there is a real FinalizationRegistry constructor, we still make it safe
// before exporting it. Rather than deleting the `constructor` property, we
// follow the other taming patterns and point it at a throw-only inert one.
defineProperties(FinalizationRegistry.prototype, {
constructor: { value: InertFinalizationRegistry },
});

harden(FinalizationRegistry);

export { FinalizationRegistry };
23 changes: 23 additions & 0 deletions packages/SwingSet/test/test-fake-weakref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import '@agoric/install-ses';
import test from 'ava';
import { WeakRef, FinalizationRegistry } from '../src/weakref';

// We don't test that WeakRefs actually work, we only make sure we can
// interact with them without crashing. This exercises the fake no-op WeakRef
// and FinalizationRegistry that our `src/weakref.js` creates on Node.js v12.
// On v14 we get real constructors.

test('weakref is callable', async t => {
const obj = {};
const wr = new WeakRef(obj);
t.is(obj, wr.deref());

const callback = () => 0;
const fr = new FinalizationRegistry(callback);
fr.register(obj);

const obj2 = {};
const handle = {};
fr.register(obj2, handle);
fr.unregister(handle);
});
3 changes: 3 additions & 0 deletions packages/SwingSet/test/test-kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@agoric/install-ses';
import test from 'ava';
import anylogger from 'anylogger';
import { initSwingStore } from '@agoric/swing-store-simple';
import { WeakRef, FinalizationRegistry } from '../src/weakref';
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';

import buildKernel from '../src/kernel/index';
Expand Down Expand Up @@ -49,6 +50,8 @@ function makeEndowments() {
hostStorage: initSwingStore().storage,
runEndOfCrank: () => {},
makeConsole,
WeakRef,
FinalizationRegistry,
};
}

Expand Down
10 changes: 9 additions & 1 deletion packages/SwingSet/test/test-liveslots.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '@agoric/install-ses';
import test from 'ava';
import { E } from '@agoric/eventual-send';
import { WeakRef, FinalizationRegistry } from '../src/weakref';
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
import { makeLiveSlots } from '../src/kernel/liveSlots';

Expand Down Expand Up @@ -37,7 +38,14 @@ function buildSyscall() {
}

function makeDispatch(syscall, build) {
const { setBuildRootObject, dispatch } = makeLiveSlots(syscall, 'vatA');
const gcTools = harden({ WeakRef, FinalizationRegistry });
const { setBuildRootObject, dispatch } = makeLiveSlots(
syscall,
'vatA',
{},
{},
gcTools,
);
setBuildRootObject(build);
return dispatch;
}
Expand Down
15 changes: 9 additions & 6 deletions packages/SwingSet/test/test-marshal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import '@agoric/install-ses';
import test from 'ava';
import { makePromiseKit } from '@agoric/promise-kit';

import { WeakRef, FinalizationRegistry } from '../src/weakref';
import { makeMarshaller } from '../src/kernel/liveSlots';

import { buildVatController } from '../src/index';

const gcTools = harden({ WeakRef, FinalizationRegistry });

async function prep() {
const config = {};
const controller = await buildVatController(config);
await controller.run();
}

test('serialize exports', t => {
const { m } = makeMarshaller();
const { m } = makeMarshaller(undefined, gcTools);
const ser = val => m.serialize(val);
const o1 = harden({});
const o2 = harden({
Expand All @@ -38,7 +41,7 @@ test('serialize exports', t => {

test('deserialize imports', async t => {
await prep();
const { m } = makeMarshaller();
const { m } = makeMarshaller(undefined, gcTools);
const a = m.unserialize({
body: '{"@qclass":"slot","index":0}',
slots: ['o-1'],
Expand All @@ -63,7 +66,7 @@ test('deserialize imports', async t => {
});

test('deserialize exports', t => {
const { m } = makeMarshaller();
const { m } = makeMarshaller(undefined, gcTools);
const o1 = harden({});
m.serialize(o1); // allocates slot=1
const a = m.unserialize({
Expand All @@ -75,7 +78,7 @@ test('deserialize exports', t => {

test('serialize imports', async t => {
await prep();
const { m } = makeMarshaller();
const { m } = makeMarshaller(undefined, gcTools);
const a = m.unserialize({
body: '{"@qclass":"slot","index":0}',
slots: ['o-1'],
Expand All @@ -94,7 +97,7 @@ test('serialize promise', async t => {
},
};

const { m } = makeMarshaller(syscall);
const { m } = makeMarshaller(syscall, gcTools);
const { promise, resolve } = makePromiseKit();
t.deepEqual(m.serialize(promise), {
body: '{"@qclass":"slot","index":0}',
Expand Down Expand Up @@ -130,7 +133,7 @@ test('unserialize promise', async t => {
},
};

const { m } = makeMarshaller(syscall);
const { m } = makeMarshaller(syscall, gcTools);
const p = m.unserialize({
body: '{"@qclass":"slot","index":0}',
slots: ['p-1'],
Expand Down
5 changes: 3 additions & 2 deletions packages/SwingSet/test/test-vpid-kernel.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// eslint-disable-next-line no-redeclare

import '@agoric/install-ses';
import test from 'ava';
import anylogger from 'anylogger';
import { initSwingStore } from '@agoric/swing-store-simple';
import { WeakRef, FinalizationRegistry } from '../src/weakref';
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';

import buildKernel from '../src/kernel/index';
Expand Down Expand Up @@ -36,6 +35,8 @@ function makeEndowments() {
hostStorage: initSwingStore().storage,
runEndOfCrank: () => {},
makeConsole,
WeakRef,
FinalizationRegistry,
};
}

Expand Down
Loading