Skip to content

Commit

Permalink
Merge pull request #3409 from Agoric/3161-vom-recognizance
Browse files Browse the repository at this point in the history
Track recognizable objects used by VOM so other objects can be GC'd
  • Loading branch information
FUDCo authored Jun 28, 2021
2 parents 3d94526 + 9be7dfc commit 628ef14
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 45 deletions.
4 changes: 4 additions & 0 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,10 @@ export default function buildKernel(
return `@${message.target} <- ${msg.method}(${argList}) : @${result}`;
} else if (message.type === 'notify') {
return `notify(vatID: ${message.vatID}, kpid: @${message.kpid})`;
// eslint-disable-next-line no-use-before-define
} else if (gcMessages.includes(message.type)) {
// prettier-ignore
return `${message.type} ${message.vatID} ${message.krefs.map(e=>`@${e}`).join(' ')}`;
} else {
return `unknown message type ${message.type}`;
}
Expand Down
10 changes: 5 additions & 5 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,10 @@ function build(
// eslint-disable-next-line no-lonely-if, no-use-before-define
if (!isVrefReachable(vref)) {
importsToDrop.push(vref);
// and retireExport if unrecognizable (TODO: needs
// VOM.vrefIsRecognizable)
// if (!vrefIsRecognizable(vref)) {
// importsToRetire.push(vref);
// }
// eslint-disable-next-line no-use-before-define
if (!isVrefRecognizable(vref)) {
importsToRetire.push(vref);
}
}
}
}
Expand Down Expand Up @@ -421,6 +420,7 @@ function build(
VirtualObjectAwareWeakMap,
VirtualObjectAwareWeakSet,
isVrefReachable,
isVrefRecognizable,
} = makeVirtualObjectManager(
syscall,
allocateExportID,
Expand Down
3 changes: 3 additions & 0 deletions packages/SwingSet/src/kernel/vatTranslator.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
clearReachableFlag(kref);
return kref;
});
kdebug(`syscall[${vatID}].dropImports(${krefs.join(' ')})`);
// we've done all the work here, during translation
return harden(['dropImports', krefs]);
}
Expand All @@ -261,6 +262,7 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
vatKeeper.deleteCListEntry(kref, vref);
return kref;
});
kdebug(`syscall[${vatID}].retireImports(${krefs.join(' ')})`);
// we've done all the work here, during translation
return harden(['retireImports', krefs]);
}
Expand All @@ -277,6 +279,7 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
vatKeeper.deleteCListEntry(kref, vref);
return kref;
});
kdebug(`syscall[${vatID}].retireExports(${krefs.join(' ')})`);
// retireExports still has work to do
return harden(['retireExports', krefs]);
}
Expand Down
54 changes: 43 additions & 11 deletions packages/SwingSet/src/kernel/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export function makeVirtualObjectManager(
* a Presence into the state of a virtual object, or the value of a
* makeWeakStore() instance. We currently never remove anything from the
* set, but eventually we'll use refcounts within virtual data to figure
* out when the vref becomes unreachable, allowing the vat do send a
* out when the vref becomes unreachable, allowing the vat to send a
* dropImport into the kernel and release the object.
*
*/
Expand All @@ -211,7 +211,7 @@ export function makeVirtualObjectManager(

// We track imports, to preserve their vrefs against syscall.dropImport
// when the Presence goes away.
function addReachablePresence(vref) {
function addReachablePresenceRef(vref) {
const { type, allocatedByVat } = parseVatSlot(vref);
if (type === 'object' && !allocatedByVat) {
reachableVrefs.add(vref);
Expand All @@ -222,6 +222,33 @@ export function makeVirtualObjectManager(
return reachableVrefs.has(vref);
}

/**
* Set of all import vrefs which are recognizable by our virtualized data.
* These were Presences at one point. We add to this set whenever we use a
* Presence as a key into a makeWeakStore() instance or an instance of
* VirtualObjectAwareWeakMap or VirtualObjectAwareWeakSet. We currently never
* remove anything from the set, but eventually we'll use refcounts to figure
* out when the vref becomes unrecognizable, allowing the vat to send a
* retireImport into the kernel.
*
*/
/** @type {Set<string>} of vrefs */
const recognizableVrefs = new Set();

function addRecognizablePresenceValue(value) {
const vref = getSlotForVal(value);
if (vref) {
const { type, allocatedByVat } = parseVatSlot(vref);
if (type === 'object' && !allocatedByVat) {
recognizableVrefs.add(vref);
}
}
}

function isVrefRecognizable(vref) {
return recognizableVrefs.has(vref);
}

/**
* Set of all Remotables which are reachable by our virtualized data, e.g.
* `makeWeakStore().set(key, remotable)` or `virtualObject.state.foo =
Expand All @@ -235,7 +262,7 @@ export function makeVirtualObjectManager(
*/
/** @type {Set<Object>} of Remotables */
const reachableRemotables = new Set();
function addReachableRemotable(vref) {
function addReachableRemotableRef(vref) {
const { type, virtual, allocatedByVat } = parseVatSlot(vref);
if (type === 'object' && !virtual && allocatedByVat) {
// exported non-virtual object: Remotable
Expand Down Expand Up @@ -330,9 +357,10 @@ export function makeVirtualObjectManager(
!syscall.vatstoreGet(vkey),
X`${q(keyName)} already registered: ${key}`,
);
addRecognizablePresenceValue(key);
const data = m.serialize(value);
data.slots.map(addReachablePresence);
data.slots.map(addReachableRemotable);
data.slots.map(addReachablePresenceRef);
data.slots.map(addReachableRemotableRef);
syscall.vatstoreSet(vkey, JSON.stringify(data));
} else {
assertKeyDoesNotExist(key);
Expand All @@ -354,9 +382,10 @@ export function makeVirtualObjectManager(
const vkey = virtualObjectKey(key);
if (vkey) {
assert(syscall.vatstoreGet(vkey), X`${q(keyName)} not found: ${key}`);
addRecognizablePresenceValue(key);
const data = m.serialize(harden(value));
data.slots.map(addReachablePresence);
data.slots.map(addReachableRemotable);
data.slots.map(addReachablePresenceRef);
data.slots.map(addReachableRemotableRef);
syscall.vatstoreSet(vkey, JSON.stringify(data));
} else {
assertKeyExists(key);
Expand Down Expand Up @@ -419,6 +448,7 @@ export function makeVirtualObjectManager(
set(key, value) {
const vkey = vrefKey(key);
if (vkey) {
addRecognizablePresenceValue(key);
virtualObjectMaps.get(this).set(vkey, value);
} else {
actualWeakMaps.get(this).set(key, value);
Expand Down Expand Up @@ -464,6 +494,7 @@ export function makeVirtualObjectManager(
add(value) {
const vkey = vrefKey(value);
if (vkey) {
addRecognizablePresenceValue(value);
virtualObjectSets.get(this).add(vkey);
} else {
actualWeakSets.get(this).add(value);
Expand Down Expand Up @@ -594,8 +625,8 @@ export function makeVirtualObjectManager(
},
set: value => {
const serializedValue = m.serialize(value);
serializedValue.slots.map(addReachablePresence);
serializedValue.slots.map(addReachableRemotable);
serializedValue.slots.map(addReachablePresenceRef);
serializedValue.slots.map(addReachableRemotableRef);
ensureState();
innerSelf.rawData[prop] = serializedValue;
innerSelf.dirty = true;
Expand Down Expand Up @@ -654,8 +685,8 @@ export function makeVirtualObjectManager(
for (const prop of Object.getOwnPropertyNames(initialData)) {
try {
const data = m.serialize(initialData[prop]);
data.slots.map(addReachablePresence);
data.slots.map(addReachableRemotable);
data.slots.map(addReachablePresenceRef);
data.slots.map(addReachableRemotableRef);
rawData[prop] = data;
} catch (e) {
console.error(`state property ${String(prop)} is not serializable`);
Expand All @@ -680,6 +711,7 @@ export function makeVirtualObjectManager(
VirtualObjectAwareWeakMap,
VirtualObjectAwareWeakSet,
isVrefReachable,
isVrefRecognizable,
flushCache: cache.flush,
makeVirtualObjectRepresentative,
});
Expand Down
15 changes: 5 additions & 10 deletions packages/SwingSet/test/gc/test-gc-vat.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,12 @@ async function dropPresence(t, dropExport) {
t.is(objs[krefA], undefined);
t.is(objs[krefB], undefined);
} else {
// until #3161 is fixed, the importing vat hasn't yet told the kernel that
// the objects are unrecognizable, so the refcounts will be 0,1
t.deepEqual(objs[krefA], [bootstrapID, 0, 1]);
t.deepEqual(objs[krefB], [bootstrapID, 0, 1]);

// but when #3161 is fixed, the objects should be retired too, so the c-list
// mappings and valToSlot tables will be empty.
// t.is(objs[krefA], undefined);
// t.is(objs[krefB], undefined);
// the objects should be retired too, so the c-list mappings and valToSlot
// tables will be empty.
t.is(objs[krefA], undefined);
t.is(objs[krefB], undefined);
}
}

test('drop presence (export retains)', t => dropPresence(t, false));
test('drop presence (export drops)', t => dropPresence(t, true));
test.skip('drop presence (export drops)', t => dropPresence(t, true));
24 changes: 20 additions & 4 deletions packages/SwingSet/test/test-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ test('bootstrap export', async t => {
kt.push([vattp0, bootstrapVatID, 'o-55']);
kt.push([fooP, bootstrapVatID, 'p+5']);
kt.push([adminDev, bootstrapVatID, 'd-70']);
checkKT(t, c, kt);
// checkKT(t, c, kt); // disabled due to cross-engine GC variation

t.deepEqual(c.dump().runQueue, [
{
Expand All @@ -270,7 +270,7 @@ test('bootstrap export', async t => {
t.deepEqual(c.dump().log, ['bootstrap.obj0.bootstrap()', 'left.foo 1']);
kt.push([right0, leftVatID, 'o-50']);
kt.push([barP, leftVatID, 'p+5']);
checkKT(t, c, kt);
// checkKT(t, c, kt); // disabled due to cross-engine GC variation

t.deepEqual(c.dump().runQueue, [
{
Expand All @@ -296,7 +296,7 @@ test('bootstrap export', async t => {
'right.obj0.bar 2 true',
]);

checkKT(t, c, kt);
// checkKT(t, c, kt); // disabled due to cross-engine GC variation

t.deepEqual(c.dump().runQueue, [
{ type: 'notify', vatID: bootstrapVatID, kpid: fooP },
Expand All @@ -311,7 +311,15 @@ test('bootstrap export', async t => {
'right.obj0.bar 2 true',
]);
removeTriple(kt, fooP, bootstrapVatID, 'p+5'); // pruned promise
checkKT(t, c, kt);

// retired imports from bootstrap vat
removeTriple(kt, vatAdminSvc, bootstrapVatID, 'o-54');
removeTriple(kt, comms0, bootstrapVatID, 'o-50');
removeTriple(kt, left0, bootstrapVatID, 'o-51');
removeTriple(kt, right0, bootstrapVatID, 'o-52');
removeTriple(kt, timer0, bootstrapVatID, 'o-53');
removeTriple(kt, vattp0, bootstrapVatID, 'o-55');
// checkKT(t, c, kt); // disabled due to cross-engine GC variation

t.deepEqual(c.dump().runQueue, [
{ type: 'notify', vatID: leftVatID, kpid: barP },
Expand All @@ -330,5 +338,13 @@ test('bootstrap export', async t => {
await c.run();

removeTriple(kt, barP, leftVatID, 'p+5'); // pruned promise

// everybody else folds up and goes home
removeTriple(kt, comms0, commsVatID, 'o+0');
removeTriple(kt, left0, leftVatID, 'o+0');
removeTriple(kt, right0, leftVatID, 'o-50');
removeTriple(kt, right0, rightVatID, 'o+0');
removeTriple(kt, timer0, timerVatID, 'o+0');
removeTriple(kt, vattp0, vatTPVatID, 'o+0');
checkKT(t, c, kt);
});
9 changes: 4 additions & 5 deletions packages/SwingSet/test/test-gc-kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1188,12 +1188,11 @@ test('device transfer', async t => {
function getRefCounts() {
return kvStore.get(`${kref}.refCount`); // e.g. "1,1"
}
// the device should hold a reachable+recognizable reference and since
// liveslots is not yet emitting `retireImport`, vat-left (which forgot
// about amy) is still holding a 'recognizable' reference, making the
// expected count 1,2 . If deviceKeeper.js failed to establish a reference,
// the device should hold a reachable+recognizable reference, vat-left (which
// forgot about amy) does not contribute to either form of revcount, making
// the expected count 1,1. If deviceKeeper.js failed to establish a reference,
// the count would have reached 0,1, and amy would have been collected.
t.is(getRefCounts(), '1,2');
t.is(getRefCounts(), '1,1');

// now tell vat-right to retrieve amy from the device
c.queueToVatExport('right', 'o+0', 'getAmy', capargs([]), 'none');
Expand Down
17 changes: 7 additions & 10 deletions packages/SwingSet/test/test-liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -736,16 +736,13 @@ test('GC syscall.dropImports', async t => {
slots: [arg],
});

const todo = false; // enable this once we have VOM.vrefIsRecognizable
if (todo) {
// and since the vat never used the Presence in a WeakMap/WeakSet, they
// cannot recognize it either, and will emit retireImports
const l3 = log.shift();
t.deepEqual(l3, {
type: 'retireImports',
slots: [arg],
});
}
// and since the vat never used the Presence in a WeakMap/WeakSet, they
// cannot recognize it either, and will emit retireImports
const l3 = log.shift();
t.deepEqual(l3, {
type: 'retireImports',
slots: [arg],
});

t.deepEqual(log, []);
});
Expand Down

0 comments on commit 628ef14

Please sign in to comment.