Skip to content

Commit b765066

Browse files
authored
Merge pull request #1924 from Agoric/1872-gctools
chore: provide GC tools (WeakRef/FinalizationRegistry) to makeLiveSlots
2 parents 54eea62 + 3949dfb commit b765066

13 files changed

+194
-13
lines changed

packages/SwingSet/src/controller.js

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { makeMeteringTransformer } from '@agoric/transform-metering';
1818
import { makeTransform } from '@agoric/transform-eventual-send';
1919
import { locateWorkerBin } from '@agoric/xs-vat-worker';
2020

21+
import { WeakRef, FinalizationRegistry } from './weakref';
2122
import { startSubprocessWorker } from './spawnSubprocessWorker';
2223
import { waitUntilQuiescent } from './waitUntilQuiescent';
2324
import { insistStorageAPI } from './storageAPI';
@@ -183,6 +184,8 @@ export async function makeSwingsetController(
183184
startSubprocessWorkerNode,
184185
startSubprocessWorkerXS,
185186
writeSlogObject,
187+
WeakRef,
188+
FinalizationRegistry,
186189
};
187190

188191
const kernelOptions = { verbose };

packages/SwingSet/src/kernel/kernel.js

+4
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export default function buildKernel(
9999
startSubprocessWorkerNode,
100100
startSubprocessWorkerXS,
101101
writeSlogObject,
102+
WeakRef,
103+
FinalizationRegistry,
102104
} = kernelEndowments;
103105
deviceEndowments = { ...deviceEndowments }; // copy so we can modify
104106
const { verbose } = kernelOptions;
@@ -529,6 +531,7 @@ export default function buildKernel(
529531
}
530532
}
531533

534+
const gcTools = harden({ WeakRef, FinalizationRegistry });
532535
const vatManagerFactory = makeVatManagerFactory({
533536
allVatPowers,
534537
kernelKeeper,
@@ -540,6 +543,7 @@ export default function buildKernel(
540543
makeNodeWorker,
541544
startSubprocessWorkerNode,
542545
startSubprocessWorkerXS,
546+
gcTools,
543547
});
544548

545549
function buildVatSyscallHandler(vatID, translators) {

packages/SwingSet/src/kernel/vatManager/factory.js

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function makeVatManagerFactory({
1414
makeNodeWorker,
1515
startSubprocessWorkerNode,
1616
startSubprocessWorkerXS,
17+
gcTools,
1718
}) {
1819
const localFactory = makeLocalVatManagerFactory({
1920
allVatPowers,
@@ -22,6 +23,7 @@ export function makeVatManagerFactory({
2223
meterManager,
2324
transformMetering,
2425
waitUntilQuiescent,
26+
gcTools,
2527
});
2628

2729
const nodeWorkerFactory = makeNodeWorkerVatManagerFactory({

packages/SwingSet/src/kernel/vatManager/localVatManager.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export function makeLocalVatManagerFactory(tools) {
1313
meterManager,
1414
transformMetering,
1515
waitUntilQuiescent,
16+
gcTools,
1617
} = tools;
1718

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

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

112113
let meterRecord = null;
113114
if (metered) {

packages/SwingSet/src/kernel/vatManager/nodeWorkerSupervisor.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import anylogger from 'anylogger';
77
import { assert } from '@agoric/assert';
88
import { importBundle } from '@agoric/import-bundle';
99
import { Remotable, getInterfaceOf, makeMarshal } from '@agoric/marshal';
10+
import { WeakRef, FinalizationRegistry } from '../../weakref';
1011
import { waitUntilQuiescent } from '../../waitUntilQuiescent';
1112
import { makeLiveSlots } from '../liveSlots';
1213

@@ -113,7 +114,8 @@ parentPort.on('message', ([type, ...margs]) => {
113114
makeMarshal,
114115
testLog,
115116
};
116-
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters);
117+
const gcTools = harden({ WeakRef, FinalizationRegistry });
118+
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters, gcTools);
117119

118120
const endowments = {
119121
...ls.vatGlobals,

packages/SwingSet/src/kernel/vatManager/subprocessSupervisor.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import fs from 'fs';
77
import { assert } from '@agoric/assert';
88
import { importBundle } from '@agoric/import-bundle';
99
import { Remotable, getInterfaceOf, makeMarshal } from '@agoric/marshal';
10+
import { WeakRef, FinalizationRegistry } from '../../weakref';
1011
import { arrayEncoderStream, arrayDecoderStream } from '../../worker-protocol';
1112
import {
1213
netstringEncoderStream,
@@ -133,7 +134,8 @@ fromParent.on('data', ([type, ...margs]) => {
133134
makeMarshal,
134135
testLog,
135136
};
136-
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters);
137+
const gcTools = harden({ WeakRef, FinalizationRegistry });
138+
const ls = makeLiveSlots(syscall, vatID, vatPowers, vatParameters, gcTools);
137139

138140
const endowments = {
139141
...ls.vatGlobals,

packages/SwingSet/src/weakref.js

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/* global globalThis */
2+
3+
import { assert, details as d } from '@agoric/assert';
4+
5+
const { defineProperties } = Object;
6+
7+
/*
8+
* We retain a measure of compatibility with Node.js v12, which does not
9+
* expose WeakRef or FinalizationRegistry (there is a --flag for it, but it's
10+
* * not clear how stable it is). When running on a platform without these *
11+
* tools, vats cannot do GC, and the tools they get will be no-ops. WeakRef
12+
* instances will hold a strong reference, and the FinalizationRegistry will
13+
* never invoke the callbacks.
14+
*
15+
* Modules should do:
16+
*
17+
* import { WeakRef, FinalizationRegistry } from '.../weakref';
18+
*
19+
*/
20+
21+
// TODO We need to migrate this into a ses-level tame-weakref.js, to happen
22+
// as part of `lockdown`. In anticipation, this uses some of the patterns
23+
// followed by the other tamings there.
24+
25+
// Emulate the internal [[WeakRefTarget]] slot. Despite the term "Weak" in the
26+
// "WeakMap" used in the emulation, this map holds the target strongly. The only
27+
// weakness here is that the weakref,target pair can go away together if the
28+
// weakref is not reachable.
29+
const weakRefTarget = new WeakMap();
30+
31+
const FakeWeakRef = function WeakRef(target) {
32+
assert(
33+
new.target !== undefined,
34+
d`WeakRef Constructor requires 'new'`,
35+
TypeError,
36+
);
37+
assert.equal(
38+
Object(target),
39+
target,
40+
d`WeakRef target must be an object`,
41+
TypeError,
42+
);
43+
weakRefTarget.set(this, target);
44+
};
45+
46+
const InertWeakRef = function WeakRef(_target) {
47+
throw new TypeError('Not available');
48+
};
49+
50+
const FakeWeakRefPrototype = {
51+
deref() {
52+
return weakRefTarget.get(this);
53+
},
54+
[Symbol.toStringTag]: 'WeakRef',
55+
};
56+
57+
defineProperties(FakeWeakRef, {
58+
prototype: { value: FakeWeakRefPrototype },
59+
});
60+
61+
const WeakRef = globalThis.WeakRef || FakeWeakRef;
62+
63+
// If there is a real WeakRef constructor, we still make it safe before
64+
// exporting it. Unlike https://github.com/tc39/ecma262/issues/2214
65+
// rather than deleting the `constructor` property, we follow the other
66+
// taming patterns and point it at a throw-only inert one.
67+
defineProperties(WeakRef.prototype, {
68+
constructor: { value: InertWeakRef },
69+
});
70+
71+
harden(WeakRef);
72+
73+
export { WeakRef };
74+
75+
// /////////////////////////////////////////////////////////////////////////////
76+
77+
const FakeFinalizationRegistry = function FinalizationRegistry(
78+
cleanupCallback,
79+
) {
80+
assert(
81+
new.target !== undefined,
82+
d`FinalizationRegistry Constructor requires 'new'`,
83+
TypeError,
84+
);
85+
assert.typeof(
86+
cleanupCallback,
87+
'function',
88+
d`cleanupCallback must be a function`,
89+
);
90+
// fall off the end with an empty instance
91+
};
92+
93+
const InertFinalizationRegistry = function FinalizationRegistry(
94+
_cleanupCallback,
95+
) {
96+
throw new TypeError('Not available');
97+
};
98+
99+
const FakeFinalizationRegistryPrototype = {
100+
register() {},
101+
unregister() {},
102+
[Symbol.toStringTag]: 'FinalizationRegistry',
103+
};
104+
105+
defineProperties(FakeFinalizationRegistry, {
106+
prototype: { value: FakeFinalizationRegistryPrototype },
107+
});
108+
109+
const FinalizationRegistry =
110+
globalThis.FinalizationRegistry || FakeFinalizationRegistry;
111+
112+
// If there is a real FinalizationRegistry constructor, we still make it safe
113+
// before exporting it. Rather than deleting the `constructor` property, we
114+
// follow the other taming patterns and point it at a throw-only inert one.
115+
defineProperties(FinalizationRegistry.prototype, {
116+
constructor: { value: InertFinalizationRegistry },
117+
});
118+
119+
harden(FinalizationRegistry);
120+
121+
export { FinalizationRegistry };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import '@agoric/install-ses';
2+
import test from 'ava';
3+
import { WeakRef, FinalizationRegistry } from '../src/weakref';
4+
5+
// We don't test that WeakRefs actually work, we only make sure we can
6+
// interact with them without crashing. This exercises the fake no-op WeakRef
7+
// and FinalizationRegistry that our `src/weakref.js` creates on Node.js v12.
8+
// On v14 we get real constructors.
9+
10+
test('weakref is callable', async t => {
11+
const obj = {};
12+
const wr = new WeakRef(obj);
13+
t.is(obj, wr.deref());
14+
15+
const callback = () => 0;
16+
const fr = new FinalizationRegistry(callback);
17+
fr.register(obj);
18+
19+
const obj2 = {};
20+
const handle = {};
21+
fr.register(obj2, handle);
22+
fr.unregister(handle);
23+
});

packages/SwingSet/test/test-kernel.js

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import '@agoric/install-ses';
22
import test from 'ava';
33
import anylogger from 'anylogger';
44
import { initSwingStore } from '@agoric/swing-store-simple';
5+
import { WeakRef, FinalizationRegistry } from '../src/weakref';
56
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
67

78
import buildKernel from '../src/kernel/index';
@@ -49,6 +50,8 @@ function makeEndowments() {
4950
hostStorage: initSwingStore().storage,
5051
runEndOfCrank: () => {},
5152
makeConsole,
53+
WeakRef,
54+
FinalizationRegistry,
5255
};
5356
}
5457

packages/SwingSet/test/test-liveslots.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import '@agoric/install-ses';
22
import test from 'ava';
33
import { E } from '@agoric/eventual-send';
4+
import { WeakRef, FinalizationRegistry } from '../src/weakref';
45
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
56
import { makeLiveSlots } from '../src/kernel/liveSlots';
67

@@ -37,7 +38,14 @@ function buildSyscall() {
3738
}
3839

3940
function makeDispatch(syscall, build) {
40-
const { setBuildRootObject, dispatch } = makeLiveSlots(syscall, 'vatA');
41+
const gcTools = harden({ WeakRef, FinalizationRegistry });
42+
const { setBuildRootObject, dispatch } = makeLiveSlots(
43+
syscall,
44+
'vatA',
45+
{},
46+
{},
47+
gcTools,
48+
);
4149
setBuildRootObject(build);
4250
return dispatch;
4351
}

packages/SwingSet/test/test-marshal.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ import '@agoric/install-ses';
22
import test from 'ava';
33
import { makePromiseKit } from '@agoric/promise-kit';
44

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

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

10+
const gcTools = harden({ WeakRef, FinalizationRegistry });
11+
912
async function prep() {
1013
const config = {};
1114
const controller = await buildVatController(config);
1215
await controller.run();
1316
}
1417

1518
test('serialize exports', t => {
16-
const { m } = makeMarshaller();
19+
const { m } = makeMarshaller(undefined, gcTools);
1720
const ser = val => m.serialize(val);
1821
const o1 = harden({});
1922
const o2 = harden({
@@ -38,7 +41,7 @@ test('serialize exports', t => {
3841

3942
test('deserialize imports', async t => {
4043
await prep();
41-
const { m } = makeMarshaller();
44+
const { m } = makeMarshaller(undefined, gcTools);
4245
const a = m.unserialize({
4346
body: '{"@qclass":"slot","index":0}',
4447
slots: ['o-1'],
@@ -63,7 +66,7 @@ test('deserialize imports', async t => {
6366
});
6467

6568
test('deserialize exports', t => {
66-
const { m } = makeMarshaller();
69+
const { m } = makeMarshaller(undefined, gcTools);
6770
const o1 = harden({});
6871
m.serialize(o1); // allocates slot=1
6972
const a = m.unserialize({
@@ -75,7 +78,7 @@ test('deserialize exports', t => {
7578

7679
test('serialize imports', async t => {
7780
await prep();
78-
const { m } = makeMarshaller();
81+
const { m } = makeMarshaller(undefined, gcTools);
7982
const a = m.unserialize({
8083
body: '{"@qclass":"slot","index":0}',
8184
slots: ['o-1'],
@@ -94,7 +97,7 @@ test('serialize promise', async t => {
9497
},
9598
};
9699

97-
const { m } = makeMarshaller(syscall);
100+
const { m } = makeMarshaller(syscall, gcTools);
98101
const { promise, resolve } = makePromiseKit();
99102
t.deepEqual(m.serialize(promise), {
100103
body: '{"@qclass":"slot","index":0}',
@@ -130,7 +133,7 @@ test('unserialize promise', async t => {
130133
},
131134
};
132135

133-
const { m } = makeMarshaller(syscall);
136+
const { m } = makeMarshaller(syscall, gcTools);
134137
const p = m.unserialize({
135138
body: '{"@qclass":"slot","index":0}',
136139
slots: ['p-1'],

packages/SwingSet/test/test-vpid-kernel.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
// eslint-disable-next-line no-redeclare
2-
31
import '@agoric/install-ses';
42
import test from 'ava';
53
import anylogger from 'anylogger';
64
import { initSwingStore } from '@agoric/swing-store-simple';
5+
import { WeakRef, FinalizationRegistry } from '../src/weakref';
76
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
87

98
import buildKernel from '../src/kernel/index';
@@ -36,6 +35,8 @@ function makeEndowments() {
3635
hostStorage: initSwingStore().storage,
3736
runEndOfCrank: () => {},
3837
makeConsole,
38+
WeakRef,
39+
FinalizationRegistry,
3940
};
4041
}
4142

0 commit comments

Comments
 (0)