Skip to content

Commit cad2b2e

Browse files
committed
feat: properly terminate & clean up after failed vats
1 parent 9985dc4 commit cad2b2e

26 files changed

+397
-119
lines changed

packages/SwingSet/src/kernel/dynamicVat.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,16 @@ export function makeDynamicVatCreator(stuff) {
7171
? '(source bundle)'
7272
: `from: ${source.bundleName}`;
7373

74-
assertKnownOptions(dynamicOptions, ['metered', 'vatParameters']);
75-
const { metered = true, vatParameters = {} } = dynamicOptions;
74+
assertKnownOptions(dynamicOptions, [
75+
'metered',
76+
'vatParameters',
77+
'enableSetup',
78+
]);
79+
const {
80+
metered = true,
81+
vatParameters = {},
82+
enableSetup = false,
83+
} = dynamicOptions;
7684
let terminated = false;
7785

7886
function notifyTermination(error) {
@@ -113,7 +121,7 @@ export function makeDynamicVatCreator(stuff) {
113121
const managerOptions = {
114122
bundle: vatSourceBundle,
115123
metered,
116-
enableSetup: false,
124+
enableSetup,
117125
enableInternalMetering: false,
118126
notifyTermination: metered ? notifyTermination : undefined,
119127
vatConsole: makeVatConsole(vatID),

packages/SwingSet/src/kernel/kernel.js

+57-34
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
6060
const logStartup = verbose ? console.debug : () => 0;
6161

6262
insistStorageAPI(hostStorage);
63-
const { enhancedCrankBuffer, commitCrank } = wrapStorage(hostStorage);
63+
const { enhancedCrankBuffer, abortCrank, commitCrank } = wrapStorage(
64+
hostStorage,
65+
);
6466

6567
const kernelSlog = writeSlogObject
6668
? makeSlogger(writeSlogObject)
@@ -283,6 +285,36 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
283285
}
284286
}
285287

288+
function removeVatManager(vatID, reason) {
289+
const old = ephemeral.vats.get(vatID);
290+
ephemeral.vats.delete(vatID);
291+
const err = reason ? Error(reason) : undefined;
292+
old.notifyTermination(err); // XXX TODO: most of the places where notifyTermination gets passed don't need it
293+
return old.manager.shutdown();
294+
}
295+
296+
function terminateVat(vatID, reason) {
297+
if (kernelKeeper.getVatKeeper(vatID)) {
298+
const promisesToReject = kernelKeeper.cleanupAfterTerminatedVat(vatID);
299+
const err = reason ? makeError(reason) : VAT_TERMINATION_ERROR;
300+
for (const kpid of promisesToReject) {
301+
resolveToError(kpid, err, vatID);
302+
}
303+
removeVatManager(vatID, reason).then(
304+
() => kdebug(`terminated vat ${vatID}`),
305+
e => console.error(`problem terminating vat ${vatID}`, e),
306+
);
307+
}
308+
}
309+
310+
let deliveryProblem;
311+
312+
function registerDeliveryProblem(vatID, problem, resultKP) {
313+
if (!deliveryProblem) {
314+
deliveryProblem = { vatID, problem, resultKP };
315+
}
316+
}
317+
286318
async function deliverAndLogToVat(vatID, kernelDelivery, vatDelivery) {
287319
const vat = ephemeral.vats.get(vatID);
288320
const crankNum = kernelKeeper.getCrankNumber();
@@ -295,10 +327,10 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
295327
try {
296328
const deliveryResult = await vat.manager.deliver(vatDelivery);
297329
finish(deliveryResult);
298-
// TODO: eventually
299-
// if (deliveryResult === death) {
300-
// vat.notifyTermination(deliveryResult.causeOfDeath);
301-
// }
330+
const [status, problem] = deliveryResult;
331+
if (status !== 'ok') {
332+
registerDeliveryProblem(vatID, problem);
333+
}
302334
} catch (e) {
303335
// log so we get a stack trace
304336
console.error(`error in kernel.deliver:`, e);
@@ -426,6 +458,7 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
426458
}
427459
try {
428460
processQueueRunning = Error('here');
461+
deliveryProblem = null;
429462
if (message.type === 'send') {
430463
kernelKeeper.decrementRefCount(message.target, `deq|msg|t`);
431464
kernelKeeper.decrementRefCount(message.msg.result, `deq|msg|r`);
@@ -441,8 +474,15 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
441474
} else {
442475
throw Error(`unable to process message.type ${message.type}`);
443476
}
444-
kernelKeeper.purgeDeadKernelPromises();
445-
kernelKeeper.saveStats();
477+
if (deliveryProblem) {
478+
abortCrank();
479+
const { vatID, problem } = deliveryProblem;
480+
terminateVat(vatID, problem);
481+
kdebug(`vat abnormally terminated: ${problem}`);
482+
} else {
483+
kernelKeeper.purgeDeadKernelPromises();
484+
kernelKeeper.saveStats();
485+
}
446486
commitCrank();
447487
kernelKeeper.incrementCrankNumber();
448488
} finally {
@@ -639,19 +679,20 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
639679
// This is a safety check -- this case should never happen unless the
640680
// vatManager is somehow confused.
641681
console.error(`vatSyscallHandler invoked on dead vat ${vatID}`);
642-
return harden(['error', 'vat is dead']);
682+
const problem = 'vat is dead';
683+
registerDeliveryProblem(vatID, problem);
684+
return harden(['error', problem]);
643685
}
644686
let ksc;
645687
try {
646688
// this can fail if the vat asks for something not on their clist,
647689
// which is fatal to the vat
648690
ksc = translators.vatSyscallToKernelSyscall(vatSyscallObject);
649691
} catch (vaterr) {
650-
console.error(`vat ${vatID} error during translation: ${vaterr}`);
651-
console.error(`vat terminated`);
652-
// TODO: mark the vat as dead, reject subsequent syscalls, withhold
653-
// deliveries, notify adminvat
654-
return harden(['error', 'clist violation: prepare to die']);
692+
kdebug(`vat ${vatID} terminated: error during translation: ${vaterr}`);
693+
const problem = 'clist violation: prepare to die';
694+
registerDeliveryProblem(vatID, problem);
695+
return harden(['error', problem]);
655696
}
656697

657698
const finish = kernelSlog.syscall(vatID, ksc, vatSyscallObject);
@@ -674,7 +715,9 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
674715
panic(`error during syscall/device.invoke: ${err}`, err);
675716
// the kernel is now in a shutdown state, but it may take a while to
676717
// grind to a halt
677-
return harden(['error', 'you killed my kernel. prepare to die']);
718+
const problem = 'you killed my kernel. prepare to die';
719+
registerDeliveryProblem(vatID, problem);
720+
return harden(['error', problem]);
678721
}
679722

680723
return vres;
@@ -717,13 +760,6 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
717760
manager.setVatSyscallHandler(vatSyscallHandler);
718761
}
719762

720-
function removeVatManager(vatID) {
721-
const old = ephemeral.vats.get(vatID);
722-
ephemeral.vats.delete(vatID);
723-
old.notifyTermination(null);
724-
return old.manager.shutdown();
725-
}
726-
727763
const knownBundles = new Map();
728764

729765
function hasBundle(name) {
@@ -843,19 +879,6 @@ export default function buildKernel(kernelEndowments, kernelOptions = {}) {
843879
// now the vatManager is attached and ready for transcript replay
844880
}
845881

846-
function terminateVat(vatID) {
847-
if (kernelKeeper.getVatKeeper(vatID)) {
848-
const promisesToReject = kernelKeeper.cleanupAfterTerminatedVat(vatID);
849-
for (const kpid of promisesToReject) {
850-
resolveToError(kpid, VAT_TERMINATION_ERROR, vatID);
851-
}
852-
removeVatManager(vatID).then(
853-
() => kdebug(`terminated vat ${vatID}`),
854-
e => console.error(`problem terminating vat ${vatID}`, e),
855-
);
856-
}
857-
}
858-
859882
if (vatAdminDeviceBundle) {
860883
// if we have a device bundle, then vats[vatAdmin] will be present too
861884
const endowments = {

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

+19-4
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,22 @@ export default function makeKernelKeeper(storage) {
487487
const DYNAMIC_IDS_KEY = 'vat.dynamicIDs';
488488
const oldDynamicVatIDs = JSON.parse(getRequired(DYNAMIC_IDS_KEY));
489489
const newDynamicVatIDs = oldDynamicVatIDs.filter(v => v !== vatID);
490-
storage.set(DYNAMIC_IDS_KEY, JSON.stringify(newDynamicVatIDs));
490+
if (newDynamicVatIDs.length !== oldDynamicVatIDs.length) {
491+
storage.set(DYNAMIC_IDS_KEY, JSON.stringify(newDynamicVatIDs));
492+
} else {
493+
console.log(`removing static vat ${vatID}`);
494+
for (const k of storage.getKeys('vat.name.', 'vat.name/')) {
495+
if (storage.get(k) === vatID) {
496+
storage.delete(k);
497+
const VAT_NAMES_KEY = 'vat.names';
498+
const name = k.slice('vat.name.'.length);
499+
const oldStaticVatNames = JSON.parse(getRequired(VAT_NAMES_KEY));
500+
const newStaticVatNames = oldStaticVatNames.filter(v => v !== name);
501+
storage.set(VAT_NAMES_KEY, JSON.stringify(newStaticVatNames));
502+
break;
503+
}
504+
}
505+
}
491506

492507
return kernelPromisesToReject;
493508
}
@@ -608,10 +623,10 @@ export default function makeKernelKeeper(storage) {
608623
*
609624
* @param kernelSlot The kernel slot whose refcount is to be incremented.
610625
*/
611-
function incrementRefCount(kernelSlot, tag) {
626+
function incrementRefCount(kernelSlot, _tag) {
612627
if (kernelSlot && parseKernelSlot(kernelSlot).type === 'promise') {
613628
const refCount = Nat(Number(storage.get(`${kernelSlot}.refCount`))) + 1;
614-
kdebug(`++ ${kernelSlot} ${tag} ${refCount}`);
629+
// kdebug(`++ ${kernelSlot} ${tag} ${refCount}`);
615630
storage.set(`${kernelSlot}.refCount`, `${refCount}`);
616631
}
617632
}
@@ -633,7 +648,7 @@ export default function makeKernelKeeper(storage) {
633648
let refCount = Nat(Number(storage.get(`${kernelSlot}.refCount`)));
634649
assert(refCount > 0, details`refCount underflow {kernelSlot} ${tag}`);
635650
refCount -= 1;
636-
kdebug(`-- ${kernelSlot} ${tag} ${refCount}`);
651+
// kdebug(`-- ${kernelSlot} ${tag} ${refCount}`);
637652
storage.set(`${kernelSlot}.refCount`, `${refCount}`);
638653
if (refCount === 0) {
639654
deadKernelPromises.add(kernelSlot);

packages/SwingSet/src/kernel/vatAdmin/vatAdminWrapper.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ export function buildRootObject(vatPowers) {
2525

2626
const [doneP, doneRR] = producePRR();
2727
running.set(vatID, doneRR);
28+
doneP.catch(() => {}); // shut up false whine about unhandled rejection
2829

2930
const adminNode = harden({
3031
terminate() {
3132
D(vatAdminNode).terminate(vatID);
32-
// TODO(hibbert): cleanup admin vat data structures
3333
},
3434
adminData() {
3535
return D(vatAdminNode).adminStats(vatID);

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

+9-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { insistMessage } from '../../message';
44
export function makeDeliver(tools, dispatch) {
55
const {
66
meterRecord,
7-
notifyTermination,
87
refillAllMeters,
98
stopGlobalMeter,
109
transcriptManager,
@@ -31,9 +30,13 @@ export function makeDeliver(tools, dispatch) {
3130
* so, and the kernel must be defensive against this.
3231
*/
3332
function runAndWait(f, errmsg) {
33+
// prettier-ignore
3434
Promise.resolve()
3535
.then(f)
36-
.then(undefined, err => console.log(`doProcess: ${errmsg}:`, err));
36+
.then(
37+
undefined,
38+
err => console.log(`doProcess: ${errmsg}: ${err.message}`),
39+
);
3740
return waitUntilQuiescent();
3841
}
3942

@@ -48,17 +51,14 @@ export function makeDeliver(tools, dispatch) {
4851
await runAndWait(() => dispatch[dispatchOp](...dispatchArgs), errmsg);
4952
stopGlobalMeter();
5053

54+
let status = ['ok'];
5155
// refill this vat's meter, if any, accumulating its usage for stats
5256
if (meterRecord) {
5357
// note that refill() won't actually refill an exhausted meter
5458
const used = meterRecord.refill();
5559
const exhaustionError = meterRecord.isExhausted();
5660
if (exhaustionError) {
57-
// TODO: if the vat requested death-before-confusion, unwind this
58-
// crank and pretend all its syscalls never happened
59-
if (notifyTermination) {
60-
notifyTermination(exhaustionError);
61-
}
61+
status = ['error', exhaustionError.message];
6262
} else {
6363
updateStats(used);
6464
}
@@ -70,12 +70,13 @@ export function makeDeliver(tools, dispatch) {
7070
// TODO: if the dispatch failed, and we choose to destroy the vat, change
7171
// what we do with the transcript here.
7272
transcriptManager.finishDispatch();
73+
return status;
7374
}
7475

7576
async function deliverOneMessage(targetSlot, msg) {
7677
insistMessage(msg);
7778
const errmsg = `vat[${vatID}][${targetSlot}].${msg.method} dispatch failed`;
78-
await doProcess(
79+
return doProcess(
7980
['deliver', targetSlot, msg.method, msg.args, msg.result],
8081
errmsg,
8182
);

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

+1-11
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,13 @@ export function makeVatManagerFactory({
5353
'vatParameters',
5454
'vatConsole',
5555
]);
56-
const {
57-
setup,
58-
bundle,
59-
enableSetup = false,
60-
metered = false,
61-
notifyTermination,
62-
} = managerOptions;
56+
const { setup, bundle, enableSetup = false } = managerOptions;
6357
assert(setup || bundle);
6458
assert(
6559
!bundle || typeof bundle === 'object',
6660
`bundle must be object, not a plain string`,
6761
);
6862
assert(!(setup && !enableSetup), `setup() provided, but not enabled`); // todo maybe useless
69-
assert(
70-
!(notifyTermination && !metered),
71-
`notifyTermination is currently useless without metered:true`,
72-
); // explicit termination will change that
7363
}
7464

7565
// returns promise for new vatManager

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ export function makeLocalVatManagerFactory(tools) {
2828
};
2929
// testLog is also a vatPower, only for unit tests
3030

31-
function prepare(vatID, managerOptions = {}) {
32-
const { notifyTermination = undefined } = managerOptions;
31+
function prepare(vatID) {
3332
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
3433
const transcriptManager = makeTranscriptManager(
3534
kernelKeeper,
@@ -46,7 +45,6 @@ export function makeLocalVatManagerFactory(tools) {
4645
{
4746
vatID,
4847
stopGlobalMeter,
49-
notifyTermination,
5048
meterRecord,
5149
refillAllMeters,
5250
transcriptManager,
@@ -65,7 +63,6 @@ export function makeLocalVatManagerFactory(tools) {
6563
setVatSyscallHandler,
6664
deliver,
6765
shutdown,
68-
notifyTermination,
6966
});
7067
return manager;
7168
}
@@ -75,7 +72,6 @@ export function makeLocalVatManagerFactory(tools) {
7572
function createFromSetup(vatID, setup, managerOptions) {
7673
assert(!managerOptions.metered, `unsupported`);
7774
assert(!managerOptions.enableInternalMetering, `unsupported`);
78-
assert(!managerOptions.notifyTermination, `unsupported`);
7975
assert(setup instanceof Function, 'setup is not an in-realm function');
8076
const { syscall, finish } = prepare(vatID, managerOptions);
8177

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

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export function makeNodeWorkerVatManagerFactory(tools) {
3030
function createFromBundle(vatID, bundle, managerOptions) {
3131
const { vatParameters } = managerOptions;
3232
assert(!managerOptions.metered, 'not supported yet');
33-
assert(!managerOptions.notifyTermination, 'not supported yet');
3433
assert(!managerOptions.enableSetup, 'not supported at all');
3534
if (managerOptions.enableInternalMetering) {
3635
// TODO: warn+ignore, rather than throw, because the kernel enables it

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

-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ export function createSyscall(transcriptManager) {
8787
// (and we'll be terminated, but the kernel and all other vats will
8888
// continue). Emit enough of an error message to explain the errors
8989
// that are about to ensue on our way down.
90-
console.error(`syscall suffered error, shutdown commencing`);
9190
throw Error(`syscall suffered error, shutdown commencing`);
9291
}
9392
// otherwise vres is ['ok', null] or ['ok', capdata]

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

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export function makeNodeSubprocessFactory(tools) {
3131
function createFromBundle(vatID, bundle, managerOptions) {
3232
const { vatParameters } = managerOptions;
3333
assert(!managerOptions.metered, 'not supported yet');
34-
assert(!managerOptions.notifyTermination, 'not supported yet');
3534
assert(!managerOptions.enableSetup, 'not supported at all');
3635
if (managerOptions.enableInternalMetering) {
3736
// TODO: warn+ignore, rather than throw, because the kernel enables it

0 commit comments

Comments
 (0)