From 37dba4263f8aa2d25da402cabdf5601130d7cd45 Mon Sep 17 00:00:00 2001 From: Chip Morningstar Date: Wed, 19 Aug 2020 13:26:16 -0700 Subject: [PATCH] fix: cope with delivery failures after replay due to dead vats --- packages/SwingSet/src/kernel/kernel.js | 7 ++-- packages/SwingSet/test/test-terminate.js | 18 -------- .../terminate/bootstrap-speak-to-dead.js | 39 +++++++++++++++++ .../terminate/swingset-speak-to-dead.json | 16 +++++++ .../vat-admin/terminate/test-terminate.js | 42 +++++++++++++++++-- .../terminate/vat-medium-terminate.js | 16 +++++++ 6 files changed, 113 insertions(+), 25 deletions(-) delete mode 100644 packages/SwingSet/test/test-terminate.js create mode 100644 packages/SwingSet/test/vat-admin/terminate/bootstrap-speak-to-dead.js create mode 100644 packages/SwingSet/test/vat-admin/terminate/swingset-speak-to-dead.json create mode 100644 packages/SwingSet/test/vat-admin/terminate/vat-medium-terminate.js diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index da60d155d1f..440d74c14a8 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -303,11 +303,10 @@ export default function buildKernel(kernelEndowments) { async function deliverToVat(vatID, target, msg) { insistMessage(msg); const vat = ephemeral.vats.get(vatID); - assert(vat, details`unknown vatID ${vatID}`); kernelKeeper.incStat('dispatches'); kernelKeeper.incStat('dispatchDeliver'); - if (vat.dead) { - resolveToError(msg.result, makeError('vat is dead')); + if (!vat || vat.dead) { + resolveToError(msg.result, makeError('unknown vat')); } else { const kd = harden(['message', target, msg]); const vd = vat.translators.kernelDeliveryToVatDelivery(kd); @@ -378,7 +377,7 @@ export default function buildKernel(kernelEndowments) { insistVatID(vatID); insistKernelType('promise', kpid); const vat = ephemeral.vats.get(vatID); - assert(vat, details`unknown vatID ${vatID}`); + assert(vat, details`notify unknown vatID ${vatID}`); kernelKeeper.incStat('dispatches'); if (vat.dead) { kdebug(`dropping notify of ${kpid} to ${vatID} because vat is dead`); diff --git a/packages/SwingSet/test/test-terminate.js b/packages/SwingSet/test/test-terminate.js deleted file mode 100644 index a81742e467b..00000000000 --- a/packages/SwingSet/test/test-terminate.js +++ /dev/null @@ -1,18 +0,0 @@ -import '@agoric/install-ses'; -import path from 'path'; -import { test } from 'tape-promise/tape'; -import { buildVatController, loadSwingsetConfigFile } from '../src/index'; - -test('terminate', async t => { - const configPath = path.resolve(__dirname, 'basedir-terminate/swingset.json'); - const config = loadSwingsetConfigFile(configPath); - const controller = await buildVatController(config); - t.equal(controller.bootstrapResult.status(), 'pending'); - await controller.run(); - t.equal(controller.bootstrapResult.status(), 'fulfilled'); - t.deepEqual(controller.bootstrapResult.resolution(), { - body: '"vat is dead"', - slots: [], - }); - t.end(); -}); diff --git a/packages/SwingSet/test/vat-admin/terminate/bootstrap-speak-to-dead.js b/packages/SwingSet/test/vat-admin/terminate/bootstrap-speak-to-dead.js new file mode 100644 index 00000000000..1e2e60b136d --- /dev/null +++ b/packages/SwingSet/test/vat-admin/terminate/bootstrap-speak-to-dead.js @@ -0,0 +1,39 @@ +/* global harden */ +import { E } from '@agoric/eventual-send'; + +export function buildRootObject(vatPowers) { + const { testLog } = vatPowers; + let mediumRoot; + let weatherwaxRoot; + + const self = harden({ + async bootstrap(vats, devices) { + const vatMaker = E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); + mediumRoot = vats.medium; + + // create a dynamic vat, then kill it, then try to send it a message + + const weatherwax = await E(vatMaker).createVatByName('weatherwax'); + weatherwaxRoot = weatherwax.root; + + E(weatherwax.adminNode).terminate(); + await E(weatherwax.adminNode).done(); + + try { + await E(mediumRoot).speak(weatherwaxRoot, '1'); + } catch (e) { + testLog(`speak failed: ${e}`); + } + + return 'bootstrap done'; + }, + async speakAgain() { + try { + await E(mediumRoot).speak(weatherwaxRoot, '2'); + } catch (e) { + testLog(`respeak failed: ${e}`); + } + }, + }); + return self; +} diff --git a/packages/SwingSet/test/vat-admin/terminate/swingset-speak-to-dead.json b/packages/SwingSet/test/vat-admin/terminate/swingset-speak-to-dead.json new file mode 100644 index 00000000000..465d4a7b2f6 --- /dev/null +++ b/packages/SwingSet/test/vat-admin/terminate/swingset-speak-to-dead.json @@ -0,0 +1,16 @@ +{ + "bootstrap": "bootstrap", + "bundles": { + "weatherwax": { + "sourceSpec": "vat-weatherwax-terminate.js" + } + }, + "vats": { + "bootstrap": { + "sourceSpec": "bootstrap-speak-to-dead.js" + }, + "medium": { + "sourceSpec": "vat-medium-terminate.js" + } + } +} diff --git a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js index 07f9582b92f..8165bcee2e7 100644 --- a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js +++ b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js @@ -43,13 +43,51 @@ test('terminate', async t => { 'GOT QUERY 3', 'foreverP.catch vat terminated', 'query3P.catch vat terminated', - 'foo4P.catch vat is dead', + 'foo4P.catch unknown vat', 'afterForeverP.catch vat terminated', 'done', ]); t.end(); }); +test('vat referencing the dead does not harm kernel', async t => { + const configPath = path.resolve(__dirname, 'swingset-speak-to-dead.json'); + const config = loadSwingsetConfigFile(configPath); + + const { storage: storage1 } = initSwingStore(); + { + const c1 = await buildVatController(copy(config), [], { + hostStorage: storage1, + }); + await c1.run(); + t.deepEqual(c1.bootstrapResult.resolution(), capargs('bootstrap done')); + t.deepEqual(c1.dump().log, ['live 1 failed: unknown vat']); + } + const state1 = getAllState(storage1); + const { storage: storage2 } = initSwingStore(); + setAllState(storage2, state1); + { + const c2 = await buildVatController(copy(config), [], { + hostStorage: storage2, + }); + const r2 = c2.queueToVatExport( + 'bootstrap', + 'o+0', + 'speakAgain', + capargs([]), + 'panic', + ); + await c2.run(); + t.equal(r2.status(), 'fulfilled'); + t.deepEqual(c2.dump().log, [ + 'live 1 failed: unknown vat', + 'live 2 failed: unknown vat', + ]); + } + + t.end(); +}); + test('replay does not resurrect dead vat', async t => { const configPath = path.resolve(__dirname, 'swingset-no-zombies.json'); const config = loadSwingsetConfigFile(configPath); @@ -59,9 +97,7 @@ test('replay does not resurrect dead vat', async t => { const c1 = await buildVatController(copy(config), [], { hostStorage: storage1, }); - t.equal(c1.bootstrapResult.status(), 'pending'); await c1.run(); - t.equal(c1.bootstrapResult.status(), 'fulfilled'); t.deepEqual(c1.bootstrapResult.resolution(), capargs('bootstrap done')); // this comes from the dynamic vat... t.deepEqual(c1.dump().log, [`I ate'nt dead`]); diff --git a/packages/SwingSet/test/vat-admin/terminate/vat-medium-terminate.js b/packages/SwingSet/test/vat-admin/terminate/vat-medium-terminate.js new file mode 100644 index 00000000000..a75e485148c --- /dev/null +++ b/packages/SwingSet/test/vat-admin/terminate/vat-medium-terminate.js @@ -0,0 +1,16 @@ +/* global harden */ +import { E } from '@agoric/eventual-send'; + +export function buildRootObject(vatPowers) { + const { testLog } = vatPowers; + + return harden({ + async speak(target, tag) { + try { + await E(target).live(); + } catch (e) { + testLog(`live ${tag} failed: ${e}`); + } + }, + }); +}