From a7d996b618e85ab59ee5c318ff13b30fa0442782 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 23 Mar 2022 18:31:19 -0700 Subject: [PATCH] feat(swingset): get a small upgrade to work This enables a partial vat upgrade test to work. It makes a lot of assumptions and is missing a lot of functionality, but it's a concrete step forward. Limitations include: * if the stopVat or startVat fail somehow, the vat will be left in an unusable state, and the caller will not be notified * stopVat does not clean up the non-durable objects * stopVat does not reject the lingering promises * startVat does not assert that userspace reconnects all Kinds * nothing about reconnecting Kinds is tested at all * nothing about remaining durable objects is tested at all * the kernel does not unsubscribe the vat from remaining promises * transcript pointers are cleared, but the contents are not deleted * the snapshot decref/deletion is not tested * stopVat is metered, but probably shouldn't be * startVat metering needs more thought refs #1848 --- packages/SwingSet/src/kernel/kernel.js | 71 ++++++++++++++----- .../SwingSet/src/kernel/state/vatKeeper.js | 22 ++++++ packages/SwingSet/src/kernel/vat-warehouse.js | 9 +++ packages/SwingSet/src/liveslots/liveslots.js | 2 + .../SwingSet/test/upgrade/test-upgrade.js | 10 +-- 5 files changed, 89 insertions(+), 25 deletions(-) diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 3ac23255ffc..c3c7d632581 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -640,33 +640,68 @@ export default function buildKernel( */ async function processUpgradeVat(message) { assert(vatAdminRootKref, `initializeKernel did not set vatAdminRootKref`); - // const { bundleID } = message; - const { vatID, upgradeID, vatParameters } = message; + const { vatID, upgradeID, bundleID, vatParameters } = message; insistCapData(vatParameters); // eslint-disable-next-line no-use-before-define assert(vatWarehouse.lookup(vatID)); + const vatKeeper = kernelKeeper.provideVatKeeper(vatID); /** @type { import('../types-external.js').KernelDeliveryStopVat } */ - const kd = harden(['stopVat']); + const kd1 = harden(['stopVat']); // eslint-disable-next-line no-use-before-define - const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd); - const status = await deliverAndLogToVat(vatID, kd, vd); + const vd1 = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd1); + const status1 = await deliverAndLogToVat(vatID, kd1, vd1); + if (status1.terminate) { + // TODO: if stopVat fails, stop now, arrange for everything to + // be unwound. TODO: we need to notify caller about the failure + console.log(`-- upgrade-vat stopVat failed: ${status1.terminate}`); + } - // TODO: if status.terminate then abort the crank, discard the - // upgrade event, and arrange to use vatUpgradeCallback to inform - // the caller + // stop the worker, delete the transcript and any snapshot + // eslint-disable-next-line no-use-before-define + await vatWarehouse.destroyWorker(vatID); + const source = { bundleID }; + const { options } = vatKeeper.getSourceAndOptions(); + vatKeeper.setSourceAndOptions(source, options); + // TODO: decref the bundleID once setSourceAndOptions increfs it - // for now, all attempts to upgrade will fail + // pause, take a deep breath, appreciate this moment of silence + // between the old and the new. this moment will never come again. - // TODO: decref the bundleID and vatParameters.slots - const args = { - body: JSON.stringify([upgradeID, false, { error: `not implemented` }]), - slots: [], - }; - queueToKref(vatAdminRootKref, 'vatUpgradeCallback', args, 'logFailure'); - // if stopVat fails, we want everything to be unwound. TODO: we - // need to notify caller about the failure - return { ...status, discardFailedDelivery: true }; + // deliver a startVat with the new vatParameters + /** @type { import('../types-external.js').KernelDeliveryStartVat } */ + const kd2 = harden(['startVat', vatParameters]); + // eslint-disable-next-line no-use-before-define + const vd2 = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd2); + // decref vatParameters now that translation did incref + for (const kref of vatParameters.slots) { + kernelKeeper.decrementRefCount(kref, 'upgrade-vat-event'); + } + const status2 = await deliverAndLogToVat(vatID, kd2, vd2); + if (status2.terminate) { + console.log(`-- upgrade-vat startVat failed: ${status2.terminate}`); + } + + if (status1.terminate || status2.terminate) { + // TODO: if status.terminate then abort the crank, discard the + // upgrade event, and arrange to use vatUpgradeCallback to inform + // the caller + console.log(`-- upgrade-vat delivery failed`); + + // TODO: this is the message we want to send on failure, but we + // need to queue it after the crank was unwound, else this + // message will be unwound too + const args = { + body: JSON.stringify([upgradeID, false, { error: `not implemented` }]), + slots: [], + }; + queueToKref(vatAdminRootKref, 'vatUpgradeCallback', args, 'logFailure'); + } else { + const args = { body: JSON.stringify([upgradeID, true]), slots: [] }; + queueToKref(vatAdminRootKref, 'vatUpgradeCallback', args, 'logFailure'); + } + // return { ...status1, ...status2, discardFailedDelivery: true }; + return {}; } function legibilizeMessage(message) { diff --git a/packages/SwingSet/src/kernel/state/vatKeeper.js b/packages/SwingSet/src/kernel/state/vatKeeper.js index 63868cb6212..5fefd27fcf4 100644 --- a/packages/SwingSet/src/kernel/state/vatKeeper.js +++ b/packages/SwingSet/src/kernel/state/vatKeeper.js @@ -567,6 +567,27 @@ export function makeVatKeeper( return true; } + function removeSnapshotAndTranscript() { + const skey = `local.${vatID}.lastSnapshot`; + const epkey = `${vatID}.t.endPosition`; + if (snapStore) { + const notation = kvStore.get(skey); + if (notation) { + const { snapshotID } = JSON.parse(notation); + if (removeFromSnapshot(snapshotID) === 0) { + // TODO: if we roll back (because the upgrade failed), we must + // not really delete the snapshot + snapStore.prepareToDelete(snapshotID); + } + kvStore.delete(skey); + } + } + // TODO: same rollback concern + // TODO: streamStore.deleteStream(transcriptStream); + const newStart = streamStore.STREAM_START; + kvStore.set(epkey, `${JSON.stringify(newStart)}`); + } + function vatStats() { function getCount(key, first) { const id = Nat(BigInt(getRequired(key))); @@ -638,5 +659,6 @@ export function makeVatKeeper( saveSnapshot, getLastSnapshot, removeFromSnapshot, + removeSnapshotAndTranscript, }); } diff --git a/packages/SwingSet/src/kernel/vat-warehouse.js b/packages/SwingSet/src/kernel/vat-warehouse.js index 2130ff7c2d8..232baeb30ea 100644 --- a/packages/SwingSet/src/kernel/vat-warehouse.js +++ b/packages/SwingSet/src/kernel/vat-warehouse.js @@ -361,6 +361,13 @@ export function makeVatWarehouse(kernelKeeper, vatLoader, policyOptions) { } } + async function destroyWorker(vatID) { + // stop any existing worker, delete transcript and any snapshot + await evict(vatID); + const vatKeeper = kernelKeeper.provideVatKeeper(vatID); + vatKeeper.removeSnapshotAndTranscript(); + } + // mostly used by tests, only needed with thread/process-based workers function shutdown() { const work = Array.from(ephemeral.vats.values(), ({ manager }) => @@ -378,6 +385,8 @@ export function makeVatWarehouse(kernelKeeper, vatLoader, policyOptions) { deliverToVat, maybeSaveSnapshot, + destroyWorker, + // mostly for testing? activeVatsInfo: () => [...ephemeral.vats].map(([id, { options }]) => ({ id, options })), diff --git a/packages/SwingSet/src/liveslots/liveslots.js b/packages/SwingSet/src/liveslots/liveslots.js index af215200f7a..0ba747f1225 100644 --- a/packages/SwingSet/src/liveslots/liveslots.js +++ b/packages/SwingSet/src/liveslots/liveslots.js @@ -1267,6 +1267,8 @@ function build( assert(didStartVat); assert(!didStopVat); didStopVat = true; + // eslint-disable-next-line no-use-before-define + await bringOutYourDead(); // empty for now } diff --git a/packages/SwingSet/test/upgrade/test-upgrade.js b/packages/SwingSet/test/upgrade/test-upgrade.js index c96e424a677..6c7e62763be 100644 --- a/packages/SwingSet/test/upgrade/test-upgrade.js +++ b/packages/SwingSet/test/upgrade/test-upgrade.js @@ -54,13 +54,9 @@ async function testUpgrade(t, defaultManagerType) { // upgrade should work const [v2status, v2capdata] = await run('upgradeV2', []); - // t.is(v2status, 'fulfilled'); - // t.deepEqual(JSON.parse(v2capdata.body), ['v2', { youAre: 'v2', marker }]); - // t.deepEqual(v2capdata.slots, [markerKref]); - - // but for now, upgrade is just a stub - t.is(v2status, 'rejected'); - t.deepEqual(JSON.parse(v2capdata.body), { error: 'not implemented' }); + t.is(v2status, 'fulfilled'); + t.deepEqual(JSON.parse(v2capdata.body), ['v2', { youAre: 'v2', marker }]); + t.deepEqual(v2capdata.slots, [markerKref]); } test('vat upgrade - local', async t => {