Skip to content

Commit

Permalink
Merge pull request #1924 from Agoric/1872-gctools
Browse files Browse the repository at this point in the history
chore: provide GC tools (WeakRef/FinalizationRegistry) to makeLiveSlots
  • Loading branch information
warner authored Oct 27, 2020
2 parents 54eea62 + 3949dfb commit b765066
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 13 deletions.
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
3 changes: 2 additions & 1 deletion packages/SwingSet/src/kernel/vatManager/localVatManager.js
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

0 comments on commit b765066

Please sign in to comment.