diff --git a/packages/SwingSet/docs/vat-worker.md b/packages/SwingSet/docs/vat-worker.md index 6ea5a7143c9..7d554206fa1 100644 --- a/packages/SwingSet/docs/vat-worker.md +++ b/packages/SwingSet/docs/vat-worker.md @@ -24,6 +24,7 @@ The `Delivery` object is a hardened Array of data (Objects, Arrays, and Strings, * `['message', targetSlot, msg]` * `['notify', resolutions]` +* `['dropExports', vrefs]` In the `message` form, `targetSlot` is a object/promise reference (a string like `o+13` or `p-24`), which identifies the object or promise to which the message is being sent. This target can be a promise if the message is being pipelined to the result of some earlier message. diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index 48f2c99b47b..2392b6a65af 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -618,6 +618,13 @@ function build( } } + function dropExports(vrefs) { + assert(Array.isArray(vrefs)); + vrefs.map(vref => insistVatType('object', vref)); + vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat)); + console.log(`-- liveslots ignoring dropExports`); + } + // TODO: when we add notifyForward, guard against cycles function exitVat(completion) { @@ -676,7 +683,7 @@ function build( slotToVal.set(rootSlot, rootObject); } - const dispatch = harden({ deliver, notify }); + const dispatch = harden({ deliver, notify, dropExports }); return harden({ vatGlobals, setBuildRootObject, dispatch, m }); } diff --git a/packages/SwingSet/src/kernel/vatManager/deliver.js b/packages/SwingSet/src/kernel/vatManager/deliver.js index c6793d13381..3113bf8eac6 100644 --- a/packages/SwingSet/src/kernel/vatManager/deliver.js +++ b/packages/SwingSet/src/kernel/vatManager/deliver.js @@ -89,6 +89,11 @@ export function makeDeliver(tools, dispatch) { return doProcess(['notify', resolutions], errmsg); } + async function deliverOneDropExports(vrefs) { + const errmsg = `vat[${vatID}].dropExports failed`; + return doProcess(['dropExports', vrefs], errmsg); + } + // vatDeliverObject is: // ['message', target, msg] // target is vid @@ -109,6 +114,8 @@ export function makeDeliver(tools, dispatch) { return deliverOneMessage(...args); case 'notify': return deliverOneNotification(...args); + case 'dropExports': + return deliverOneDropExports(...args); default: assert.fail(X`unknown delivery type ${type}`); } diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js b/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js index 0d4a11df122..ea21d14eeac 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js @@ -64,6 +64,11 @@ function doNotify(resolutions) { return doProcess(['notify', resolutions], errmsg); } +function doDropExports(vrefs) { + const errmsg = `vat.doDropExport failed`; + return doProcess(['dropExports', vrefs], errmsg); +} + parentPort.on('message', ([type, ...margs]) => { workerLog(`received`, type); if (type === 'start') { @@ -143,6 +148,8 @@ parentPort.on('message', ([type, ...margs]) => { doMessage(...dargs).then(res => sendUplink(['deliverDone', ...res])); } else if (dtype === 'notify') { doNotify(...dargs).then(res => sendUplink(['deliverDone', ...res])); + } else if (dtype === 'dropExports') { + doDropExports(...dargs).then(res => sendUplink(['deliverDone', ...res])); } else { assert.fail(X`bad delivery type ${dtype}`); } diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js index dc8a261f329..fddfe8d1902 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js @@ -64,6 +64,11 @@ function doNotify(resolutions) { return doProcess(['notify', resolutions], errmsg); } +function doDropExports(vrefs) { + const errmsg = `vat.doDropExport failed`; + return doProcess(['dropExports', vrefs], errmsg); +} + const toParent = arrayEncoderStream(); toParent .pipe(netstringEncoderStream()) @@ -163,6 +168,8 @@ fromParent.on('data', ([type, ...margs]) => { doMessage(...dargs).then(res => sendUplink(['deliverDone', ...res])); } else if (dtype === 'notify') { doNotify(...dargs).then(res => sendUplink(['deliverDone', ...res])); + } else if (dtype === 'dropExports') { + doDropExports(...dargs).then(res => sendUplink(['deliverDone', ...res])); } else { assert.fail(X`bad delivery type ${dtype}`); } diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js index 4f95d874cd4..b5df65c5956 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js @@ -151,6 +151,12 @@ function makeWorker(port) { return doProcess(['notify', resolutions], errmsg); } + /** @type { (rs: unknown) => Promise } */ + function doDropExports(vrefs) { + const errmsg = `vat.dropExports failed`; + return doProcess(['dropExports', vrefs], errmsg); + } + /** * TODO: consider other methods per SES VirtualConsole. * See https://github.com/Agoric/agoric-sdk/issues/2146 @@ -277,6 +283,8 @@ function makeWorker(port) { return doMessage(dargs[0], dargs[1]); case 'notify': return doNotify(dargs[0]); + case 'dropExports': + return doDropExports(dargs[0]); default: assert.fail(X`bad delivery type ${dtype}`); } diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index f3cc5ce09c6..1b4376154ca 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -1,5 +1,5 @@ import { assert, details as X } from '@agoric/assert'; -import { makeVatSlot } from '../../parseVatSlots'; +import { makeVatSlot, insistVatType, parseVatSlot } from '../../parseVatSlots'; import { getRemote } from './remote'; import { makeState, makeStateKit } from './state'; import { deliverToController } from './controller'; @@ -109,7 +109,14 @@ export function buildCommsDispatch( // comms vat version of it) here? } - const dispatch = harden({ deliver, notify }); + function dropExports(vrefs) { + assert(Array.isArray(vrefs)); + vrefs.map(vref => insistVatType('object', vref)); + vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat)); + console.log(`-- comms ignoring dropExports`); + } + + const dispatch = harden({ deliver, notify, dropExports }); debugState.set(dispatch, { state, clistKit }); return dispatch; diff --git a/packages/SwingSet/test/test-comms.js b/packages/SwingSet/test/test-comms.js index c566830963a..194f5f23640 100644 --- a/packages/SwingSet/test/test-comms.js +++ b/packages/SwingSet/test/test-comms.js @@ -155,18 +155,19 @@ test('receive', t => { encodeArgs(`deliver:${bobRemote}:bar::ro-20:${bobRemote};argsbytes`), null, ); + const expectedAliceKernel = 'o+31'; t.deepEqual(sends.shift(), [ bobKernel, 'bar', - capdata('argsbytes', ['o+31', bobKernel]), + capdata('argsbytes', [expectedAliceKernel, bobKernel]), ]); // if we were to send o+31/lo11, the other side should get ro+20, which is alice t.is(getRemoteForLocal(remoteID, 'lo11'), 'ro+20'); t.is(getLocalForRemote(remoteID, 'ro-20'), 'lo11'); - t.is(getKernelForLocal('lo11'), 'o+31'); - t.is(getLocalForKernel('o+31'), 'lo11'); + t.is(getKernelForLocal('lo11'), expectedAliceKernel); + t.is(getLocalForKernel(expectedAliceKernel), 'lo11'); - // bob!bar(alice, bob) + // bob!bar(alice, bob), again, to test stability d.deliver( receiverID, 'receive', @@ -176,10 +177,11 @@ test('receive', t => { t.deepEqual(sends.shift(), [ bobKernel, 'bar', - capdata('argsbytes', ['o+31', bobKernel]), + capdata('argsbytes', [expectedAliceKernel, bobKernel]), ]); // bob!cat(alice, bob, ayana) + const expectedAyanaKernel = 'o+32'; d.deliver( receiverID, 'receive', @@ -189,6 +191,9 @@ test('receive', t => { t.deepEqual(sends.shift(), [ bobKernel, 'cat', - capdata('argsbytes', ['o+31', bobKernel, 'o+32']), + capdata('argsbytes', [expectedAliceKernel, bobKernel, expectedAyanaKernel]), ]); + + // make sure comms can tolerate dropExports, even if it's a no-op + d.dropExports([expectedAliceKernel, expectedAyanaKernel]); }); diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index c637d9dec7f..17b0f5a3fcb 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -15,8 +15,17 @@ function capargs(args, slots = []) { return capdata(JSON.stringify(args), slots); } -function caponeslot(slot) { - return capargs([{ '@qclass': 'slot', index: 0 }], [slot]); +function capdataOneSlot(slot) { + return capargs({ '@qclass': 'slot', iface: 'Alleged: export', index: 0 }, [ + slot, + ]); +} + +function capargsOneSlot(slot) { + return capargs( + [{ '@qclass': 'slot', iface: 'Alleged: export', index: 0 }], + [slot], + ); } function oneResolution(promiseID, rejected, data) { @@ -639,7 +648,7 @@ test('disavow', async t => { const import1 = 'o-1'; // root~.one(import1) // sendOnly - dispatch.deliver(rootA, 'one', caponeslot(import1), undefined); + dispatch.deliver(rootA, 'one', capargsOneSlot(import1), undefined); await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'dropImports', slots: [import1] }); t.deepEqual(log.shift(), 'disavowed pres1'); @@ -672,3 +681,37 @@ test('disavow', async t => { t.deepEqual(log.shift(), 'tried to send to disavowed'); t.deepEqual(log, []); }); + +test('dropExports', async t => { + const { log, syscall } = buildSyscall(); + + function build(_vatPowers) { + const ex1 = Far('export', {}); + const root = Far('root', { + one() { + return ex1; + }, + }); + return root; + } + const dispatch = makeDispatch(syscall, build, true); + t.deepEqual(log, []); + const rootA = 'o+0'; + + // rp1 = root~.one() + // ex1 = await rp1 + const rp1 = 'p-1'; + dispatch.deliver(rootA, 'one', capargs([]), rp1); + await waitUntilQuiescent(); + const l1 = log.shift(); + const ex1 = l1.resolutions[0][2].slots[0]; + t.deepEqual(l1, { + type: 'resolve', + resolutions: [[rp1, false, capdataOneSlot(ex1)]], + }); + t.deepEqual(log, []); + + // now tell the vat to drop that export + dispatch.dropExports([ex1]); + // for now, all that we care about is that liveslots doesn't crash +});