Skip to content

Commit 7fa2661

Browse files
committed
feat: clean up after dead vats
1 parent b7dde66 commit 7fa2661

File tree

15 files changed

+180
-101
lines changed

15 files changed

+180
-101
lines changed

packages/SwingSet/src/kernel/dynamicVat.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function makeDynamicVatCreator(stuff) {
4949
function createVatDynamically(source, dynamicOptions = {}) {
5050
const vatID = allocateUnusedVatID();
5151
kernelKeeper.addDynamicVatID(vatID);
52-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
52+
const vatKeeper = kernelKeeper.allocateVatKeeper(vatID);
5353
vatKeeper.setSourceAndOptions(source, dynamicOptions);
5454
// eslint-disable-next-line no-use-before-define
5555
return create(vatID, source, dynamicOptions, true);

packages/SwingSet/src/kernel/id.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ export function insistVatID(s) {
2626
if (s !== `${s}`) {
2727
throw new Error(`not a string`);
2828
}
29-
if (!s.startsWith(`v`)) {
30-
throw new Error(`does not start with 'v'`);
29+
if (s !== 'none') {
30+
if (!s.startsWith(`v`)) {
31+
throw new Error(`does not start with 'v'`);
32+
}
33+
Nat(Number(s.slice(1)));
3134
}
32-
Nat(Number(s.slice(1)));
3335
} catch (e) {
3436
throw new Error(`'${s} is not a 'vNN'-style VatID: ${e.message}`);
3537
}

packages/SwingSet/src/kernel/kernel.js

+24-29
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ function makeError(s) {
3939
return harden({ body: JSON.stringify(s), slots: [] });
4040
}
4141

42+
const VAT_TERMINATION_ERROR = makeError('vat terminated');
43+
const UNKNOWN_VAT_ERROR = makeError('unknown vat');
44+
4245
export default function buildKernel(kernelEndowments, kernelOptions = {}) {
4346
const {
4447
waitUntilQuiescent,
@@ -173,7 +176,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
173176
insistVatID(forVatID);
174177
const kernelSlot = `${what}`;
175178
parseKernelSlot(what);
176-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(forVatID);
179+
const vatKeeper = kernelKeeper.getVatKeeper(forVatID);
177180
return vatKeeper.mapKernelSlotToVatSlot(kernelSlot);
178181
}
179182

@@ -184,7 +187,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
184187
}
185188
insistVatID(fromVatID);
186189
assert(parseVatSlot(vatSlot).allocatedByVat);
187-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(fromVatID);
190+
const vatKeeper = kernelKeeper.getVatKeeper(fromVatID);
188191
return vatKeeper.mapVatSlotToKernelSlot(vatSlot);
189192
}
190193

@@ -309,8 +312,8 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
309312
const vat = ephemeral.vats.get(vatID);
310313
kernelKeeper.incStat('dispatches');
311314
kernelKeeper.incStat('dispatchDeliver');
312-
if (!vat || vat.dead) {
313-
resolveToError(msg.result, makeError('unknown vat'));
315+
if (!vat || vat.dead || vatID === 'none') {
316+
resolveToError(msg.result, UNKNOWN_VAT_ERROR);
314317
} else {
315318
const kd = harden(['message', target, msg]);
316319
const vd = vat.translators.kernelDeliveryToVatDelivery(kd);
@@ -323,7 +326,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
323326
const { type } = parseKernelSlot(target);
324327
if (type === 'object') {
325328
const vatID = kernelKeeper.ownerOfKernelObject(target);
326-
insistVatID(vatID);
329+
vatID === 'none' || insistVatID(vatID);
327330
await deliverToVat(vatID, target, msg);
328331
} else if (type === 'promise') {
329332
const kp = kernelKeeper.getKernelPromise(target);
@@ -553,7 +556,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
553556
},
554557
}); // marker
555558
vatObj0s[name] = vref;
556-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
559+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
557560
const kernelSlot = vatKeeper.mapVatSlotToKernelSlot(vatSlot);
558561
vrefs.set(vref, kernelSlot);
559562
logStartup(`adding vref ${name} [${vatID}]`);
@@ -690,9 +693,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
690693
enablePipelining = false,
691694
notifyTermination = () => {},
692695
} = managerOptions;
693-
// This should create the vatKeeper. Other users get it from the
694-
// kernelKeeper, so we don't need a reference ourselves.
695-
kernelKeeper.allocateVatKeeperIfNeeded(vatID);
696+
kernelKeeper.getVatKeeper(vatID);
696697
const translators = makeVatTranslators(vatID, kernelKeeper);
697698

698699
ephemeral.vats.set(
@@ -788,7 +789,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
788789

789790
function collectVatStats(vatID) {
790791
insistVatID(vatID);
791-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
792+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
792793
return vatKeeper.vatStats();
793794
}
794795

@@ -810,6 +811,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
810811
const vatID = kernelKeeper.allocateVatIDForNameIfNeeded(name);
811812
logStartup(`Assigned VatID ${vatID} for genesis vat ${name}`);
812813
kernelSlog.addVat(vatID, false, name);
814+
kernelKeeper.allocateVatKeeper(vatID);
813815
const managerOptions = harden({
814816
...genesisVats.get(name),
815817
vatConsole: makeVatConsole(vatID),
@@ -824,33 +826,26 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
824826
// instantiate all dynamic vats
825827
for (const vatID of kernelKeeper.getAllDynamicVatIDs()) {
826828
logStartup(`Loading dynamic vat ${vatID}`);
827-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
828-
if (vatKeeper.isDead()) {
829-
kernelKeeper.forgetVat(vatID);
830-
} else {
831-
const {
832-
source,
833-
options: dynamicOptions,
834-
} = vatKeeper.getSourceAndOptions();
835-
// eslint-disable-next-line no-await-in-loop
836-
await recreateVatDynamically(vatID, source, dynamicOptions);
837-
// now the vatManager is attached and ready for transcript replay
838-
}
829+
const vatKeeper = kernelKeeper.allocateVatKeeper(vatID);
830+
const {
831+
source,
832+
options: dynamicOptions,
833+
} = vatKeeper.getSourceAndOptions();
834+
// eslint-disable-next-line no-await-in-loop
835+
await recreateVatDynamically(vatID, source, dynamicOptions);
836+
// now the vatManager is attached and ready for transcript replay
839837
}
840838

841839
function terminateVat(vatID) {
842-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
843-
if (!vatKeeper.isDead()) {
844-
vatKeeper.markAsDead();
845-
const err = makeError('vat terminated');
846-
for (const kpid of kernelKeeper.findPromisesDecidedByVat(vatID)) {
847-
resolveToError(kpid, err, vatID);
840+
if (kernelKeeper.getVatKeeper(vatID)) {
841+
const promisesToReject = kernelKeeper.cleanupAfterTerminatedVat(vatID);
842+
for (const kpid of promisesToReject) {
843+
resolveToError(kpid, VAT_TERMINATION_ERROR, vatID);
848844
}
849845
removeVatManager(vatID).then(
850846
() => kdebug(`terminated vat ${vatID}`),
851847
e => console.error(`problem terminating vat ${vatID}`, e),
852848
);
853-
kernelKeeper.forgetVat(vatID);
854849
}
855850
}
856851

packages/SwingSet/src/kernel/state/kernelKeeper.js

+66-42
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,42 @@ export default function makeKernelKeeper(storage) {
432432
storage.set(`${kernelSlot}.data.slots`, capdata.slots.join(','));
433433
}
434434

435-
function findPromisesDecidedByVat(vatID) {
436-
const prefixKey = `${vatID}.c.p`;
437-
const endKey = `${vatID}.c.q`;
438-
const result = [];
439-
for (const k of storage.getKeys(prefixKey, endKey)) {
435+
function cleanupAfterTerminatedVat(vatID) {
436+
insistVatID(vatID);
437+
ephemeral.vatKeepers.delete(vatID);
438+
const koPrefix = `${vatID}.c.o+`;
439+
const kpPrefix = `${vatID}.c.p`;
440+
const kernelPromisesToReject = [];
441+
for (const k of storage.getKeys(`${vatID}.`, `${vatID}/`)) {
440442
// The store semantics ensure this iteration is lexicographic. Any
441443
// changes to the creation of the list of promises need to preserve this
442444
// in order to preserve determinism.
443-
const kpid = storage.get(k);
444-
const p = getKernelPromise(kpid);
445-
if (p.state === 'unresolved' && p.decider === vatID) {
446-
result.push(kpid);
445+
if (k.startsWith(koPrefix)) {
446+
const koid = storage.get(k);
447+
const ownerKey = `${koid}.owner`;
448+
const ownerVat = storage.get(ownerKey);
449+
if (ownerVat === vatID) {
450+
storage.set(ownerKey, 'none');
451+
}
452+
} else if (k.startsWith(kpPrefix)) {
453+
const kpid = storage.get(k);
454+
const p = getKernelPromise(kpid);
455+
if (p.state === 'unresolved' && p.decider === vatID) {
456+
kernelPromisesToReject.push(kpid);
457+
}
447458
}
459+
storage.delete(k);
448460
}
449-
return result;
461+
// TODO: deleting entries from the dynamic vat IDs list requires a linear
462+
// scan; this collection ought to be represented in a way that makes it
463+
// efficient to remove an entry from it, though this should be OK as long as
464+
// we keep the list short.
465+
const DYNAMIC_IDS_KEY = 'vat.dynamicIDs';
466+
const oldDynamicVatIDs = JSON.parse(getRequired(DYNAMIC_IDS_KEY));
467+
const newDynamicVatIDs = oldDynamicVatIDs.filter(v => v !== vatID);
468+
storage.set(DYNAMIC_IDS_KEY, JSON.stringify(newDynamicVatIDs));
469+
470+
return kernelPromisesToReject;
450471
}
451472

452473
function addMessageToPromiseQueue(kernelSlot, msg) {
@@ -622,30 +643,32 @@ export default function makeKernelKeeper(storage) {
622643
deadKernelPromises.clear();
623644
}
624645

625-
function allocateVatKeeperIfNeeded(vatID) {
646+
function getVatKeeper(vatID) {
626647
insistVatID(vatID);
627-
if (!storage.has(`${vatID}.o.nextID`)) {
628-
initializeVatState(storage, vatID);
629-
}
630-
if (!ephemeral.vatKeepers.has(vatID)) {
631-
const vk = makeVatKeeper(
632-
storage,
633-
vatID,
634-
addKernelObject,
635-
addKernelPromiseForVat,
636-
incrementRefCount,
637-
decrementRefCount,
638-
incStat,
639-
decStat,
640-
);
641-
ephemeral.vatKeepers.set(vatID, vk);
642-
}
643648
return ephemeral.vatKeepers.get(vatID);
644649
}
645650

646-
function forgetVat(vatID) {
651+
function allocateVatKeeper(vatID) {
647652
insistVatID(vatID);
648-
ephemeral.vatKeepers.delete(vatID);
653+
if (!storage.has(`${vatID}.o.nextID`)) {
654+
initializeVatState(storage, vatID);
655+
}
656+
assert(
657+
!ephemeral.vatKeepers.has(vatID),
658+
details`vatID ${vatID} already defined`,
659+
);
660+
const vk = makeVatKeeper(
661+
storage,
662+
vatID,
663+
addKernelObject,
664+
addKernelPromiseForVat,
665+
incrementRefCount,
666+
decrementRefCount,
667+
incStat,
668+
decStat,
669+
);
670+
ephemeral.vatKeepers.set(vatID, vk);
671+
return vk;
649672
}
650673

651674
function getAllVatIDs() {
@@ -726,16 +749,17 @@ export default function makeKernelKeeper(storage) {
726749
const kernelTable = [];
727750

728751
for (const vatID of getAllVatIDs()) {
729-
const vk = allocateVatKeeperIfNeeded(vatID);
730-
731-
// TODO: find some way to expose the liveSlots internal tables, the
732-
// kernel doesn't see them
733-
const vatTable = {
734-
vatID,
735-
state: { transcript: Array.from(vk.getTranscript()) },
736-
};
737-
vatTables.push(vatTable);
738-
vk.dumpState().forEach(e => kernelTable.push(e));
752+
const vk = getVatKeeper(vatID);
753+
if (vk) {
754+
// TODO: find some way to expose the liveSlots internal tables, the
755+
// kernel doesn't see them
756+
const vatTable = {
757+
vatID,
758+
state: { transcript: Array.from(vk.getTranscript()) },
759+
};
760+
vatTables.push(vatTable);
761+
vk.dumpState().forEach(e => kernelTable.push(e));
762+
}
739763
}
740764

741765
for (const deviceID of getAllDeviceIDs()) {
@@ -815,7 +839,6 @@ export default function makeKernelKeeper(storage) {
815839
fulfillKernelPromiseToPresence,
816840
fulfillKernelPromiseToData,
817841
rejectKernelPromise,
818-
findPromisesDecidedByVat,
819842
addMessageToPromiseQueue,
820843
addSubscriberToPromise,
821844
setDecider,
@@ -831,8 +854,9 @@ export default function makeKernelKeeper(storage) {
831854
getVatIDForName,
832855
allocateVatIDForNameIfNeeded,
833856
allocateUnusedVatID,
834-
allocateVatKeeperIfNeeded,
835-
forgetVat,
857+
allocateVatKeeper,
858+
getVatKeeper,
859+
cleanupAfterTerminatedVat,
836860
getAllVatNames,
837861
addDynamicVatID,
838862
getAllDynamicVatIDs,

packages/SwingSet/src/kernel/state/vatKeeper.js

-10
Original file line numberDiff line numberDiff line change
@@ -179,14 +179,6 @@ export function makeVatKeeper(
179179
}
180180
}
181181

182-
function markAsDead() {
183-
storage.set(`${vatID}.dead`, 'true');
184-
}
185-
186-
function isDead() {
187-
return !!storage.get(`${vatID}.dead`);
188-
}
189-
190182
/**
191183
* Generator function to return the vat's transcript, one entry at a time.
192184
*/
@@ -252,8 +244,6 @@ export function makeVatKeeper(
252244
mapVatSlotToKernelSlot,
253245
mapKernelSlotToVatSlot,
254246
deleteCListEntry,
255-
markAsDead,
256-
isDead,
257247
getTranscript,
258248
addToTranscript,
259249
vatStats,

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function makeLocalVatManagerFactory(tools) {
3030

3131
function prepare(vatID, managerOptions = {}) {
3232
const { notifyTermination = undefined } = managerOptions;
33-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
33+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
3434
const transcriptManager = makeTranscriptManager(
3535
kernelKeeper,
3636
vatKeeper,

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function makeNodeWorkerVatManagerFactory(tools) {
3838
// stops doing that, turn this into a regular assert
3939
console.log(`node-worker does not support enableInternalMetering`);
4040
}
41-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
41+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
4242
const transcriptManager = makeTranscriptManager(
4343
kernelKeeper,
4444
vatKeeper,

packages/SwingSet/src/kernel/vatManager/worker-subprocess-node.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function makeNodeSubprocessFactory(tools) {
3939
// stops doing that, turn this into a regular assert
4040
console.log(`node-worker does not support enableInternalMetering`);
4141
}
42-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
42+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
4343
const transcriptManager = makeTranscriptManager(
4444
kernelKeeper,
4545
vatKeeper,

packages/SwingSet/src/kernel/vatTranslator.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { deleteCListEntryIfEasy } from './cleanup';
1212
* objects
1313
*/
1414
function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
15-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
15+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
1616
const { mapKernelSlotToVatSlot } = vatKeeper;
1717

1818
// msg is { method, args, result }, all slots are kernel-centric
@@ -96,7 +96,7 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
9696
* objects
9797
*/
9898
function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
99-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
99+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
100100
const { mapVatSlotToKernelSlot } = vatKeeper;
101101

102102
function translateSend(targetSlot, method, args, resultSlot) {
@@ -240,7 +240,7 @@ function makeTranslateKernelSyscallResultToVatSyscallResult(
240240
vatID,
241241
kernelKeeper,
242242
) {
243-
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
243+
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
244244

245245
const { mapKernelSlotToVatSlot } = vatKeeper;
246246

0 commit comments

Comments
 (0)