Skip to content

Commit

Permalink
feat: Phase 1a of vat termination: reject promises from the dead vat
Browse files Browse the repository at this point in the history
  • Loading branch information
FUDCo committed Aug 18, 2020
1 parent a4a173f commit 80fc527
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 31 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 @@ -808,6 +808,10 @@ export default function buildKernel(kernelEndowments) {
const vatKeeper = kernelKeeper.allocateVatKeeperIfNeeded(vatID);
if (!vatKeeper.isDead()) {
vatKeeper.markAsDead();
const err = makeError('vat terminated');
for (const kpid of kernelKeeper.findPromisesFromDeadVat(vatID)) {
deliverToError(kpid, err, vatID);
}
removeVatManager(vatID).then(
() => kdebug(`terminated vat ${vatID}`),
e => console.error(`problem terminating vat ${vatID}`, e),
Expand Down
17 changes: 16 additions & 1 deletion packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export default function makeKernelKeeper(storage) {
}

function setInitialized() {
storage.set('initialized', true);
storage.set('initialized', 'true');
}

function getCrankNumber() {
Expand Down Expand Up @@ -432,6 +432,20 @@ export default function makeKernelKeeper(storage) {
storage.set(`${kernelSlot}.data.slots`, capdata.slots.join(','));
}

function findPromisesFromDeadVat(vatID) {
const prefixKey = `${vatID}.c.p`;
const endKey = `${vatID}.c.q`;
const result = [];
for (const k of storage.getKeys(prefixKey, endKey)) {
const kpid = storage.get(k);
const p = getKernelPromise(kpid);
if (p.state === 'unresolved' && p.decider === vatID) {
result.push(kpid);
}
}
return result;
}

function addMessageToPromiseQueue(kernelSlot, msg) {
insistKernelType('promise', kernelSlot);

Expand Down Expand Up @@ -793,6 +807,7 @@ export default function makeKernelKeeper(storage) {
fulfillKernelPromiseToPresence,
fulfillKernelPromiseToData,
rejectKernelPromise,
findPromisesFromDeadVat,
addMessageToPromiseQueue,
addSubscriberToPromise,
setDecider,
Expand Down
4 changes: 2 additions & 2 deletions packages/SwingSet/src/kernel/state/vatKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ export function makeVatKeeper(
}

function markAsDead() {
storage.set(`${vatID}.dead`, true);
storage.set(`${vatID}.dead`, 'true');
}

function isDead() {
return storage.get(`${vatID}.dead`);
return !!storage.get(`${vatID}.dead`);
}

/**
Expand Down
35 changes: 10 additions & 25 deletions packages/SwingSet/test/vat-admin/terminate/bootstrap-terminate.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ export function buildRootObject(vatPowers) {
// the first will trigger another outgoing query ..
const query3P = E(dude.root).elsewhere(self, 3);
query3P.then(
answer => testLog(`3P.then ${answer}`),
err => testLog(`3P.catch ${err}`),
answer => testLog(`query3P.then ${answer}`),
err => testLog(`query3P.catch ${err}`),
);
// .. but it will be killed ..
E(dude.adminNode).terminate();
Expand All @@ -70,53 +70,40 @@ export function buildRootObject(vatPowers) {
// [adminNode.terminate, dude.foo(4), adminNode.terminate, self.query(3)]

// then terminate() is delivered, and the vat is killed. The kernel pushes a
// message to vatAdmin to let the done() promise resolve. (PHASE 2) The kernel also
// message to vatAdmin to let the done() promise resolve. The kernel also
// looks for the unresolved promises decided by the late vat, and rejects them,
// which pushes notify events on the queue
// (PHASE 1) run-queue is:
// [dude.foo(4), adminNode.terminate, self.query(3), vatAdmin.fireDone]
// (PHASE 2) run-queue is:
// run-queue is:
// [dude.foo(4), adminNode.terminate, self.query(3), vatAdmin.fireDone,
// self.notify(foreverP), self.notify(afterForeverP)]

// now dude.foo(4) comes up for delivery, and deliverToVat notices the
// target is dead, so the kernel rejects the result, pushing another
// notify
// (PHASE 1) run-queue is:
// [adminNode.terminate, self.query(3), vatAdmin.fireDone, self.notify(foo4P)]
// (PHASE 2) run-queue is:
// run-queue is:
// [adminNode.terminate, self.query(3), vatAdmin.fireDone,
// self.notify(foreverP), self.notify(afterForeverP), self.notify(foo4P)]

// now the duplicate terminate() comes up, and vatAdminVat should ignore it
// (PHASE 1) run-queue is:
// [self.query(3), vatAdmin.fireDone, self.notify(foo4P)]
// (PHASE 2) run-queue is:
// run-queue is:
// [self.query(3), vatAdmin.fireDone, self.notify(foreverP),
// self.notify(afterForeverP), self.notify(foo4P)]

// now the self.query(3) gets delivered, which pushes 'GOT QUERY 3' onto testLog, and
// resolves the result promise. The dead vat is the only subscriber, so the kernel
// pushes a notify event to vat-dude for it (which will never be delivered)
// (PHASE 1) run-queue is:
// [vatAdmin.fireDone, self.notify(foo4P), dude.notify(answerP)]
// (PHASE 2) run-queue is:
// run-queue is:
// [vatAdmin.fireDone, self.notify(foreverP), self.notify(afterForeverP),
// self.notify(foo4P), dude.notify(answerP)]

// now vatAdmin gets fireDone, which resolves the 'done' promise we've been awaiting for,
// which pushes a notify
// (PHASE 1) run-queue is:
// [self.notify(foo4P), dude.notify(answerP), self.notify(doneP)]
// (PHASE 2) run-queue is:
// run-queue is:
// [self.notify(foreverP), self.notify(afterForeverP), self.notify(foo4P),
// dude.notify(answerP), self.notify(doneP)]

// PHASE 2: we receive the rejection of foreverP, pushing
// 'foreverP.catch (err)' to testLog
// PHASE 2: we receive the rejection of afterForeverP, pushing
// 'afterForeverP.catch (err)' to testLog

// We receive the rejection of foreverP, pushing 'foreverP.catch (err)' to testLog
// We receive the rejection of afterForeverP, pushing 'afterForeverP.catch (err)' to testLog
// run-queue is:
// [self.notify(foo4P), dude.notify(answerP), self.notify(doneP)]

Expand All @@ -130,8 +117,6 @@ export function buildRootObject(vatPowers) {

// We finally hear about doneP resolving, allowing the bootstrap to
// proceed to the end of the test. We push the 'done' message to testLog
// CHIP TODO PHASE1: (or defer to phase1.5): uncomment, wire up
// queueToExport(vatAdminVat) to make it fire 'done'
await doneP;
testLog('done');

Expand Down
6 changes: 3 additions & 3 deletions packages/SwingSet/test/vat-admin/terminate/test-terminate.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ test('terminate', async t => {
'query2 2',
'QUERY 3',
'GOT QUERY 3',
// these two will be added in phase 2
// 'foreverP.catch (err??)',
// 'afterForeverP.catch (err??)',
'foreverP.catch vat terminated',
'query3P.catch vat terminated',
'foo4P.catch vat is dead',
'afterForeverP.catch vat terminated',
'done',
]);
t.end();
Expand Down

0 comments on commit 80fc527

Please sign in to comment.