Skip to content

Commit

Permalink
fix: tweaks from review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
FUDCo committed Sep 30, 2020
1 parent aa5b93c commit bccad6b
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 20 deletions.
2 changes: 1 addition & 1 deletion packages/SwingSet/docs/static-vats.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ This is particularly useful for vats that implement a REPL, so operators can inc

### vat termination: `exitVat` and `exitVatWithFailure`

A vat may signal to the kernel that it should be terminated at the end of its current crank. Two powers are provided to do this: `exitVat(completion)` and `exitVatWithFailure(reason)`. These powers will work in any vat but are primarily useful in dynamic vats. The two differ in how the circumstances of termination are signalled to holders of the vat's `done` promise: `exitVat` fulfills that promise with the value provided in the `completion` parameter, whereas `exitVatWithFailure` rejects the promise with the value provided in the `reason` parameter. Conventionally, `completion` will be a string and `reason` will be an `Error`, but any serializable object may be used for either. After the crank in which either of these powers is invoked, no further messages will be delivered to the vat; instead, any such messages will be rejected with a `'vat terminated'` error. Any outstanding promises for which the vat was the decider will also be rejected in the same way. The vat and any resources it holds will become eligible for garbage collection.
A vat may signal to the kernel that it should be terminated at the end of its current crank. Two powers are provided to do this: `exitVat(completion)` and `exitVatWithFailure(reason)`. These powers will work in any vat but are primarily useful in dynamic vats. The two differ in how the circumstances of termination are signalled to holders of the vat's `done` promise: `exitVat` fulfills that promise with the value provided in the `completion` parameter, whereas `exitVatWithFailure` rejects the promise with the value provided in the `reason` parameter. Conventionally, `completion` will be a string and `reason` will be an `Error`, but any serializable object may be used for either. After the crank in which either of these powers is invoked, no further messages will be delivered to the vat; instead, any such messages will be rejected with a `'vat terminated'` error. Any outstanding promises for which the vat was the decider will also be rejected in the same way. The vat and any resources it holds will become eligible for garbage collection. However, the crank itself will end normally, meaning that any actions taken by the vat during the crank in which either exit power was invoked will become part of the persisted state of the swingset, including messages that were sent from the vat during that crank (including, notably, actions taken _after_ the exit power was invoked but before the crank finished).

## Configuring Vats

Expand Down
7 changes: 4 additions & 3 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,8 @@ export default function buildKernel(
insistCapData(info);
if (kernelKeeper.getVatKeeper(vatID)) {
const promisesToReject = kernelKeeper.cleanupAfterTerminatedVat(vatID);
const err = VAT_TERMINATION_ERROR;
for (const kpid of promisesToReject) {
resolveToError(kpid, err, vatID);
resolveToError(kpid, VAT_TERMINATION_ERROR, vatID);
}
removeVatManager(vatID, shouldReject, info).then(
() => kdebug(`terminated vat ${vatID}`),
Expand All @@ -334,7 +333,9 @@ export default function buildKernel(
let terminationTrigger;

function setTerminationTrigger(vatID, shouldAbortCrank, shouldReject, info) {
assert(!(shouldAbortCrank && !shouldReject));
if (shouldAbortCrank) {
assert(shouldReject);
}
if (!terminationTrigger || shouldAbortCrank) {
terminationTrigger = { vatID, shouldAbortCrank, shouldReject, info };
}
Expand Down
4 changes: 2 additions & 2 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -522,11 +522,11 @@ function build(
}

function exitVat(completion) {
syscall.exit(false, m.serialize(completion));
syscall.exit(false, m.serialize(harden(completion)));
}

function exitVatWithFailure(reason) {
syscall.exit(true, m.serialize(reason));
syscall.exit(true, m.serialize(harden(reason)));
}

// vats which use D are in: acorn-eventual-send, cosmic-swingset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function buildRootObject() {
},
async phase2() {
// terminate as a second phase, so we can capture the kernel state in between
E(dude.adminNode).terminate();
E(dude.adminNode).terminate('phase 2');
await E(dude.adminNode).done();
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { E } from '@agoric/eventual-send';

export function buildRootObject(vatPowers) {
const { testLog } = vatPowers;

const self = harden({
async bootstrap(vats, devices) {
testLog('preparing dynamic vat');
const vatMaker = E(vats.vatAdmin).createVatAdminService(devices.vatAdmin);
const dude = await E(vatMaker).createVatByName('dude');
E(dude.root).dieReturningAPresence(self);
const doneP = E(dude.adminNode).done();
try {
const doneResult = await doneP;
testLog(`done message: ${doneResult.message}`);
await E(doneResult.emissary).talkBack('from beyond?');
} catch (e) {
testLog(`done exception ${e} (this should not happen)`);
}
testLog('done');
},

talkBack(arg) {
testLog(`talkback ${arg}`);
},
});
return self;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function buildRootObject() {
// sure everything is working
const weatherwax = await E(vatMaker).createVatByName('weatherwax');
await E(weatherwax.root).live();
E(weatherwax.adminNode).terminate();
E(weatherwax.adminNode).terminate('no zombies?');
try {
await E(weatherwax.adminNode).done();
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function buildRootObject(vatPowers) {
testLog(`b: speak failed: ${e}`);
}

E(weatherwax.adminNode).terminate();
E(weatherwax.adminNode).terminate('arbitrary reason');
try {
await E(weatherwax.adminNode).done();
} catch (e) {
Expand Down
15 changes: 12 additions & 3 deletions packages/SwingSet/test/vat-admin/terminate/bootstrap-terminate.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,21 @@ export function buildRootObject(vatPowers, vatParameters) {
case 'exceptionallyHappy':
E(dude.root).dieHappy(Error(mode));
break;
case 'happyTalkFirst':
E(dude.root).dieHappyButTalkToMeFirst(self, mode);
break;
case 'sad':
E(dude.root).dieSad(mode);
break;
case 'exceptionallySad':
E(dude.root).dieSad(Error(mode));
break;
case 'sadTalkFirst':
E(dude.root).dieSadButTalkToMeFirst(self, Error(mode));
break;
case 'dieReturningAPresence':
E(dude.root).dieReturningAPresence(self, Error(mode));
break;
default:
console.log('something terrible has happened');
break;
Expand All @@ -77,7 +86,7 @@ export function buildRootObject(vatPowers, vatParameters) {
);
// then we try to kill the vat again, which should be idempotent
if (mode === 'kill') {
E(dude.adminNode).terminate();
E(dude.adminNode).terminate('because we said so');
}

// the run-queue should now look like:
Expand Down Expand Up @@ -140,9 +149,9 @@ export function buildRootObject(vatPowers, vatParameters) {
// proceed to the end of the test. We push the 'done' message to testLog
try {
const v = await doneP;
testLog(`done result ${v}`);
testLog(`done result ${v} (Error=${v instanceof Error})`);
} catch (e) {
testLog(`done exception ${e}`);
testLog(`done exception ${e} (Error=${e instanceof Error})`);
}
testLog('done');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"bootstrap": "bootstrap",
"bundles": {
"dude": {
"sourceSpec": "vat-dude-terminate.js"
}
},
"vats": {
"bootstrap": {
"sourceSpec": "bootstrap-die-with-presence.js"
}
}
}
48 changes: 40 additions & 8 deletions packages/SwingSet/test/vat-admin/terminate/test-terminate.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test.before(async t => {
t.context.data = { kernelBundles };
});

async function doTerminate(t, mode, reference) {
async function doTerminate(t, mode, reference, extraMessage = []) {
const configPath = path.resolve(__dirname, 'swingset-terminate.json');
const config = loadSwingsetConfigFile(configPath);
const controller = await buildVatController(config, [mode], t.context.data);
Expand All @@ -45,6 +45,7 @@ async function doTerminate(t, mode, reference) {
'query2 2',
'QUERY 3',
'GOT QUERY 3',
...extraMessage,
'foreverP.catch Error: vat terminated',
'query3P.catch Error: vat terminated',
'foo4P.catch Error: vat terminated',
Expand All @@ -55,33 +56,64 @@ async function doTerminate(t, mode, reference) {
}

test('terminate', async t => {
await doTerminate(t, 'kill', 'done exception kill');
await doTerminate(t, 'kill', 'done exception kill (Error=false)');
});

test('exit happy path simple result', async t => {
await doTerminate(t, 'happy', 'done result happy');
await doTerminate(t, 'happy', 'done result happy (Error=false)');
});

test('exit happy path complex result', async t => {
await doTerminate(
t,
'exceptionallyHappy',
'done result Error: exceptionallyHappy',
'done result Error: exceptionallyHappy (Error=true)',
);
});

test('exit sad path simple result', async t => {
await doTerminate(t, 'sad', 'done exception sad');
await doTerminate(t, 'sad', 'done exception sad (Error=false)');
});

test('exit sad path complex result', async t => {
await doTerminate(
t,
'exceptionallySad',
'done exception Error: exceptionallySad',
'done exception Error: exceptionallySad (Error=true)',
);
});

test('exit happy path with ante-mortem message', async t => {
await doTerminate(
t,
'happyTalkFirst',
'done result happyTalkFirst (Error=false)',
['GOT QUERY not dead quite yet'],
);
});

test('exit sad path with ante-mortem message', async t => {
await doTerminate(
t,
'sadTalkFirst',
'done exception Error: sadTalkFirst (Error=true)',
['GOT QUERY not dead quite yet (but soon)'],
);
});

test('exit with presence', async t => {
const configPath = path.resolve(__dirname, 'swingset-die-with-presence.json');
const config = loadSwingsetConfigFile(configPath);
const controller = await buildVatController(config, [], t.context.data);
await controller.run();
t.deepEqual(controller.dump().log, [
'preparing dynamic vat',
'done message: your ad here',
'talkback from beyond?',
'done',
]);
});

test('dispatches to the dead do not harm kernel', async t => {
const configPath = path.resolve(__dirname, 'swingset-speak-to-dead.json');
const config = loadSwingsetConfigFile(configPath);
Expand All @@ -99,7 +131,7 @@ test('dispatches to the dead do not harm kernel', async t => {
`w: I ate'nt dead`,
'b: p1b = I so resolve',
'b: p2b fails Error: vat terminated',
'done: undefined',
'done: arbitrary reason',
]);
}
const state1 = getAllState(storage1);
Expand All @@ -122,7 +154,7 @@ test('dispatches to the dead do not harm kernel', async t => {
t.deepEqual(c2.dump().log, [
'b: p1b = I so resolve',
'b: p2b fails Error: vat terminated',
'done: undefined',
'done: arbitrary reason',
'm: live 2 failed: Error: vat terminated',
]);
}
Expand Down
14 changes: 14 additions & 0 deletions packages/SwingSet/test/vat-admin/terminate/vat-dude-terminate.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ export function buildRootObject(vatPowers) {
vatPowers.exitVatWithFailure(reason);
},

dieHappyButTalkToMeFirst(other, completion) {
vatPowers.exitVat(completion);
E(other).query('not dead quite yet');
},

dieSadButTalkToMeFirst(other, reason) {
vatPowers.exitVatWithFailure(reason);
E(other).query('not dead quite yet (but soon)');
},

dieReturningAPresence(other) {
vatPowers.exitVat({ message: 'your ad here', emissary: other });
},

async elsewhere(other, arg) {
testLog(`QUERY ${arg}`);
const answer = await E(other).query(arg);
Expand Down

0 comments on commit bccad6b

Please sign in to comment.