Skip to content

Commit

Permalink
fix(swingset): abandon/delete most non-durables during stopVat()
Browse files Browse the repository at this point in the history
This deletes most non-durable data during upgrade. stopVat() delegates
to a new function `releaseOldState()`, which makes an incomplete
effort to drop everything.

The portions which are complete are:

* find all locally-decided promises and rejects them
* find all exported Remotables and virtual objects, and abandons them
* simulate finalizers for all in-RAM Presences and Representatives
* use collectionManager to delete all virtual collections
* perform a bringOutYourDead to clean up resulting dead references

After that, `deleteVirtualObjectsWithoutDecref` walks the vatstore and
deletes the data from all virtual objects, without attempting to
decref the things they pointed to. This fails to release durables and
imports which were referenced by those virtual objects (e.g. cycles
that escaped the earlier purge).

Code is written, but not yet complete, to decref those objects
properly. A later update to this file will activate that (and update
the tests to confirm it works).

The new unit test constructs a large object graph and examines it
afterwards to make sure everything was deleted appropriately. The test
knows about the limitations of `deleteVirtualObjectsWithoutDecref`, as
well as bug #5053 which causes some other objects to be retained
incorrectly.

refs #1848
  • Loading branch information
warner committed Apr 9, 2022
1 parent c09c338 commit 80f1498
Show file tree
Hide file tree
Showing 10 changed files with 873 additions and 61 deletions.
26 changes: 25 additions & 1 deletion packages/SwingSet/src/liveslots/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function makeCollectionManager(
serialize,
unserialize,
) {
const allVirtualCollectionObjIDs = new Set();
const storeKindIDToName = new Map();

const storeKindInfo = {
Expand Down Expand Up @@ -125,6 +126,10 @@ export function makeCollectionManager(
return storeKindInfo[kindName].kindID;
}

function collectionExists(collectionID) {
return !!syscall.vatstoreGet(prefixc(collectionID, '|nextOrdinal'));
}

// Not that it's only used for this purpose, what should it be called?
// TODO Should we be using the new encodeBigInt scheme instead, anyway?
const BIGINT_TAG_LEN = 10;
Expand Down Expand Up @@ -552,8 +557,15 @@ export function makeCollectionManager(

function deleteCollection(vobjID) {
const { id, subid } = parseVatSlot(vobjID);
if (!collectionExists(subid)) {
return false; // already deleted
}
const kindName = storeKindIDToName.get(`${id}`);
const collection = summonCollectionInternal(false, 'GC', subid, kindName);
const { durable } = storeKindInfo[kindName];
if (!durable) {
allVirtualCollectionObjIDs.delete(vobjID);
}

const doMoreGC = collection.clearInternal(true);
let priorKey = '';
Expand All @@ -568,6 +580,14 @@ export function makeCollectionManager(
return doMoreGC;
}

function deleteAllVirtualCollections() {
const vobjIDs = Array.from(allVirtualCollectionObjIDs).sort();
for (const vobjID of vobjIDs) {
deleteCollection(vobjID);
allVirtualCollectionObjIDs.delete(vobjID);
}
}

function makeCollection(label, kindName, keySchema, valueSchema) {
assert.typeof(label, 'string');
assert(storeKindInfo[kindName]);
Expand All @@ -582,7 +602,10 @@ export function makeCollectionManager(
const vobjID = `o+${kindID}/${collectionID}`;

syscall.vatstoreSet(prefixc(collectionID, '|nextOrdinal'), '1');
const { hasWeakKeys } = storeKindInfo[kindName];
const { durable, hasWeakKeys } = storeKindInfo[kindName];
if (!durable) {
allVirtualCollectionObjIDs.add(vobjID);
}
if (!hasWeakKeys) {
syscall.vatstoreSet(prefixc(collectionID, '|entryCount'), '0');
}
Expand Down Expand Up @@ -828,6 +851,7 @@ export function makeCollectionManager(

return harden({
initializeStoreKindInfo,
deleteAllVirtualCollections,
makeScalarBigMapStore,
makeScalarBigWeakMapStore,
makeScalarBigSetStore,
Expand Down
66 changes: 35 additions & 31 deletions packages/SwingSet/src/liveslots/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { insistMessage } from '../lib/message.js';
import { makeVirtualReferenceManager } from './virtualReferences.js';
import { makeVirtualObjectManager } from './virtualObjectManager.js';
import { makeCollectionManager } from './collectionManager.js';
import { releaseOldState } from './stop-vat.js';

const DEFAULT_VIRTUAL_OBJECT_CACHE_SIZE = 3; // XXX ridiculously small value to force churn for testing

Expand Down Expand Up @@ -1265,32 +1266,6 @@ function build(
vom.insistAllDurableKindsReconnected();
}

/**
* @returns { Promise<void> }
*/
async function stopVat() {
assert(didStartVat);
assert(!didStopVat);
didStopVat = true;

// Pretend that userspace rejected all non-durable promises. We
// basically do the same thing that `thenReject(p,
// vpid)(rejection)` would have done, but we skip ahead to the
// syscall.resolve part. The real `thenReject` also does
// pRec.reject(), which would give control to userspace (who might
// have re-imported the promise and attached a .then to it), and
// stopVat() must not allow userspace to gain agency.

const rejectCapData = m.serialize('vat upgraded');
const vpids = Array.from(deciderVPIDs.keys()).sort();
const rejections = vpids.map(vpid => [vpid, true, rejectCapData]);
if (rejections.length) {
syscall.resolve(rejections);
}
// eslint-disable-next-line no-use-before-define
await bringOutYourDead();
}

/**
* @param { VatDeliveryObject } delivery
* @returns { void | Promise<void> }
Expand Down Expand Up @@ -1330,10 +1305,6 @@ function build(
result = startVat(vpCapData);
break;
}
case 'stopVat': {
result = stopVat();
break;
}
default:
assert.fail(X`unknown delivery type ${type}`);
}
Expand All @@ -1354,6 +1325,37 @@ function build(
return undefined;
}

/**
* @returns { Promise<void> }
*/
async function stopVat() {
assert(didStartVat);
assert(!didStopVat);
didStopVat = true;

try {
await releaseOldState({
m,
deciderVPIDs,
syscall,
exportedRemotables,
addToPossiblyDeadSet,
slotToVal,
valToSlot,
dropExports,
retireExports,
vrm,
collectionManager,
bringOutYourDead,
vreffedObjectRegistry,
});
} catch (e) {
console.log(`-- error during stopVat()`);
console.log(e);
throw e;
}
}

/**
* Do things that should be done (such as flushing caches to disk) after a
* dispatch has completed and user code has relinquished agency.
Expand Down Expand Up @@ -1402,9 +1404,11 @@ function build(
*/
async function dispatch(delivery) {
// We must short-circuit dispatch to bringOutYourDead here because it has to
// be async
// be async, same for stopVat
if (delivery[0] === 'bringOutYourDead') {
return meterControl.runWithoutMeteringAsync(bringOutYourDead);
} else if (delivery[0] === 'stopVat') {
return meterControl.runWithoutMeteringAsync(stopVat);
} else {
// Start user code running, record any internal liveslots errors. We do
// *not* directly wait for the userspace function to complete, nor for
Expand Down
Loading

0 comments on commit 80f1498

Please sign in to comment.