diff --git a/packages/SwingSet/src/kernel/kernelSyscall.js b/packages/SwingSet/src/kernel/kernelSyscall.js index 135856eb46c..2de6b226b40 100644 --- a/packages/SwingSet/src/kernel/kernelSyscall.js +++ b/packages/SwingSet/src/kernel/kernelSyscall.js @@ -119,6 +119,18 @@ export function makeKernelSyscallHandler(tools) { return OKNULL; } + function retireImports(koids) { + assert(Array.isArray(koids), X`retireImports given non-Array ${koids}`); + console.log(`-- kernel ignoring retireImports ${koids.join(',')}`); + return OKNULL; + } + + function retireExports(koids) { + assert(Array.isArray(koids), X`retireExports given non-Array ${koids}`); + console.log(`-- kernel ignoring retireExports ${koids.join(',')}`); + return OKNULL; + } + function doKernelSyscall(ksc) { const [type, ...args] = ksc; switch (type) { @@ -140,6 +152,10 @@ export function makeKernelSyscallHandler(tools) { return vatstoreDelete(...args); case 'dropImports': return dropImports(...args); + case 'retireImports': + return retireImports(...args); + case 'retireExports': + return retireExports(...args); default: assert.fail(X`unknown vatSyscall type ${type}`); } @@ -147,9 +163,6 @@ export function makeKernelSyscallHandler(tools) { const kernelSyscallHandler = harden({ send, // TODO remove these individual ones - invoke, - subscribe, - resolve, doKernelSyscall, }); return kernelSyscallHandler; diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index 2aa4e9e8d33..9e69ff81b53 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -742,6 +742,20 @@ function build( console.log(`-- liveslots ignoring dropExports`); } + function retireExports(vrefs) { + assert(Array.isArray(vrefs)); + vrefs.map(vref => insistVatType('object', vref)); + vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat)); + console.log(`-- liveslots ignoring retireExports`); + } + + function retireImports(vrefs) { + assert(Array.isArray(vrefs)); + vrefs.map(vref => insistVatType('object', vref)); + vrefs.map(vref => assert(!parseVatSlot(vref).allocatedByVat)); + console.log(`-- liveslots ignoring retireImports`); + } + // TODO: when we add notifyForward, guard against cycles function exitVat(completion) { @@ -821,6 +835,16 @@ function build( dropExports(vrefs); break; } + case 'retireExports': { + const [vrefs] = args; + retireExports(vrefs); + break; + } + case 'retireImports': { + const [vrefs] = args; + retireImports(vrefs); + break; + } default: assert.fail(X`unknown delivery type ${type}`); } diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js b/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js index ea4b9aae40d..aacad4c2c83 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js @@ -158,6 +158,8 @@ function makeSupervisorSyscall(syscallToManager, workerCanBlock) { resolve: resolutions => doSyscall(['resolve', resolutions]), exit: (isFailure, data) => doSyscall(['exit', isFailure, data]), dropImports: vrefs => doSyscall(['dropImports', vrefs]), + retireImports: vrefs => doSyscall(['retireImports', vrefs]), + retireExports: vrefs => doSyscall(['retireExports', vrefs]), // These syscalls should be omitted if the worker cannot get a // synchronous return value back from the kernel, such as when the worker diff --git a/packages/SwingSet/src/kernel/vatTranslator.js b/packages/SwingSet/src/kernel/vatTranslator.js index b9de1d21bb1..ec3f5e71272 100644 --- a/packages/SwingSet/src/kernel/vatTranslator.js +++ b/packages/SwingSet/src/kernel/vatTranslator.js @@ -185,20 +185,43 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { } function translateDropImports(vrefs) { - assert(Array.isArray(vrefs), X`dropImport() given non-Array ${vrefs}`); - // We delete clist entries as we translate, which will (TODO) decref the - // krefs. When we're done with that loop, we hand the set of krefs to - // kernelSyscall so it can (TODO) check newly-decremented refcounts - // against zero, and maybe delete even more. + assert(Array.isArray(vrefs), X`dropImports() given non-Array ${vrefs}`); + // TODO: dropImports does not affect the c-list, but makes the kernel + // calculate the refcount of a kref, possibly causing further action. const krefs = vrefs.map(vref => { - insistVatType('object', vref); + insistVatType('object', vref); // TODO: probably device nodes too const kref = mapVatSlotToKernelSlot(vref); - vatKeeper.deleteCListEntry(kref, vref); return kref; }); return harden(['dropImports', krefs]); } + function translateRetireImports(vrefs) { + assert(Array.isArray(vrefs), X`retireImports() given non-Array ${vrefs}`); + // TODO: retireImports will delete clist entries as we translate, which + // will (TODO) decref the krefs. When we're done with that loop, we hand + // the set of krefs to kernelSyscall so it can (TODO) check + // newly-decremented refcounts against zero, and maybe delete even more. + const krefs = vrefs.map(vref => { + insistVatType('object', vref); // TODO: probably device nodes too + const kref = mapVatSlotToKernelSlot(vref); + // vatKeeper.deleteCListEntry(kref, vref); + return kref; + }); + return harden(['retireImports', krefs]); + } + + function translateRetireExports(vrefs) { + assert(Array.isArray(vrefs), X`retireExports() given non-Array ${vrefs}`); + // TODO: not sure how the kernel should react to this yet + const krefs = vrefs.map(vref => { + insistVatType('object', vref); // TODO: probably device nodes too + const kref = mapVatSlotToKernelSlot(vref); + return kref; + }); + return harden(['retireExports', krefs]); + } + function translateCallNow(target, method, args) { insistCapData(args); const dev = mapVatSlotToKernelSlot(target); @@ -276,6 +299,10 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { return translateVatstoreDelete(...args); case 'dropImports': return translateDropImports(...args); + case 'retireImports': + return translateRetireImports(...args); + case 'retireExports': + return translateRetireExports(...args); default: assert.fail(X`unknown vatSyscall type ${type}`); } diff --git a/packages/SwingSet/src/message.js b/packages/SwingSet/src/message.js index dc9cba7a48c..654613e5dbf 100644 --- a/packages/SwingSet/src/message.js +++ b/packages/SwingSet/src/message.js @@ -55,7 +55,9 @@ export function insistVatDeliveryObject(vdo) { } break; } - case 'dropExports': { + case 'dropExports': + case 'retireExports': + case 'retireImports': { const [slots] = rest; assert(Array.isArray(slots)); for (const slot of slots) { @@ -150,7 +152,9 @@ export function insistVatSyscallObject(vso) { assert.typeof(key, 'string'); break; } - case 'dropImports': { + case 'dropImports': + case 'retireImports': + case 'retireExports': { const [slots] = rest; assert(Array.isArray(slots)); for (const slot of slots) { diff --git a/packages/SwingSet/src/types.js b/packages/SwingSet/src/types.js index 73f03277578..411ee9df088 100644 --- a/packages/SwingSet/src/types.js +++ b/packages/SwingSet/src/types.js @@ -70,7 +70,11 @@ * @typedef { [tag: 'message', target: string, msg: Message]} VatDeliveryMessage * @typedef { [tag: 'notify', resolutions: string[] ]} VatDeliveryNotify * @typedef { [tag: 'dropExports', vrefs: string[] ]} VatDeliveryDropExports - * @typedef { VatDeliveryMessage | VatDeliveryNotify | VatDeliveryDropExports } VatDeliveryObject + * @typedef { [tag: 'retireExports', vrefs: string[] ]} VatDeliveryRetireExports + * @typedef { [tag: 'retireImports', vrefs: string[] ]} VatDeliveryRetireImports + * @typedef { VatDeliveryMessage | VatDeliveryNotify | VatDeliveryDropExports + * | VatDeliveryRetireExports | VatDeliveryRetireImports + * } VatDeliveryObject * @typedef { [tag: 'ok', message: null, usage: unknown] | [tag: 'error', message: string, usage: unknown | null] } VatDeliveryResult * * @typedef { [tag: 'send', target: string, msg: Message] } VatSyscallSend @@ -82,10 +86,13 @@ * @typedef { [tag: 'vatstoreSet', key: string, data: string ]} VatSyscallVatstoreSet * @typedef { [tag: 'vatstoreDelete', key: string ]} VatSyscallVatstoreDelete * @typedef { [tag: 'dropImports', slots: string[] ]} VatSyscallDropImports + * @typedef { [tag: 'retireImports', slots: string[] ]} VatSyscallRetireImports + * @typedef { [tag: 'retireExports', slots: string[] ]} VatSyscallRetireExports * * @typedef { VatSyscallSend | VatSyscallCallNow | VatSyscallSubscribe * | VatSyscallResolve | VatSyscallVatstoreGet | VatSyscallVatstoreSet * | VatSyscallVatstoreDelete | VatSyscallDropImports + * | VatSyscallRetireImports | VatSyscallRetireExports * } VatSyscallObject * * @typedef { [tag: 'ok', data: SwingSetCapData | string | null ]} VatSyscallResultOk diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index b653b00781c..bace1d64a5b 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -128,6 +128,20 @@ export function buildCommsDispatch( console.log(`-- comms ignoring dropExports`); } + function retireExports(vrefs) { + assert(Array.isArray(vrefs)); + vrefs.map(vref => insistVatType('object', vref)); + vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat)); + console.log(`-- comms ignoring retireExports`); + } + + function retireImports(vrefs) { + assert(Array.isArray(vrefs)); + vrefs.map(vref => insistVatType('object', vref)); + vrefs.map(vref => assert(!parseVatSlot(vref).allocatedByVat)); + console.log(`-- comms ignoring retireImports`); + } + function dispatch(vatDeliveryObject) { const [type, ...args] = vatDeliveryObject; switch (type) { @@ -147,6 +161,16 @@ export function buildCommsDispatch( dropExports(vrefs); return; } + case 'retireExports': { + const [vrefs] = args; + retireExports(vrefs); + break; + } + case 'retireImports': { + const [vrefs] = args; + retireImports(vrefs); + break; + } default: assert.fail(X`unknown delivery type ${type}`); } diff --git a/packages/SwingSet/test/liveslots-helpers.js b/packages/SwingSet/test/liveslots-helpers.js index 99d32403d07..a2092274559 100644 --- a/packages/SwingSet/test/liveslots-helpers.js +++ b/packages/SwingSet/test/liveslots-helpers.js @@ -17,6 +17,12 @@ export function buildSyscall() { dropImports(slots) { log.push({ type: 'dropImports', slots }); }, + retireImports(slots) { + log.push({ type: 'retireImports', slots }); + }, + retireExports(slots) { + log.push({ type: 'retireExports', slots }); + }, exit(isFailure, info) { log.push({ type: 'exit', isFailure, info }); }, diff --git a/packages/SwingSet/test/test-comms.js b/packages/SwingSet/test/test-comms.js index 92a31fecb00..4f0c642b50b 100644 --- a/packages/SwingSet/test/test-comms.js +++ b/packages/SwingSet/test/test-comms.js @@ -5,7 +5,12 @@ import { flipRemoteSlot } from '../src/vats/comms/parseRemoteSlot'; import { makeState } from '../src/vats/comms/state'; import { makeCListKit } from '../src/vats/comms/clist'; import { debugState } from '../src/vats/comms/dispatch'; -import { makeMessage, makeDropExports } from './util'; +import { + makeMessage, + makeDropExports, + makeRetireExports, + makeRetireImports, +} from './util'; import { commsVatDriver } from './commsVatDriver'; test('provideRemoteForLocal', t => { @@ -229,8 +234,18 @@ test('receive', t => { { message: /unexpected recv seqNum .*/ }, ); - // make sure comms can tolerate dropExports, even if it's a no-op + // make sure comms can tolerate GC operations, even if they're a no-op dispatch(makeDropExports(expectedAliceKernel, expectedAyanaKernel)); + dispatch(makeRetireExports(expectedAliceKernel, expectedAyanaKernel)); + // Sending retireImport into a vat that hasn't yet emitted dropImport is + // rude, and would only happen if the exporter unilaterally revoked the + // object's identity. Normally the kernel would only send retireImport + // after receiving dropImport (and sending a dropExport into the exporter, + // and getting a retireExport from the exporter, gracefully terminating the + // object's identity). We do it the rude way because it's good enough to + // test the comms vat can tolerate it, but we may have to update this when + // we implement retireImport for real. + dispatch(makeRetireImports(bobKernel)); }); // This tests the various pathways through the comms vat driver. This has the diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index 3dba1eb4172..ec5c93641f2 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -15,6 +15,8 @@ import { makeResolve, makeReject, makeDropExports, + makeRetireExports, + makeRetireImports, } from './util'; test('calls', async t => { @@ -630,13 +632,13 @@ test('disavow', async t => { t.deepEqual(log, []); }); -test('dropExports', async t => { +test('GC operations', async t => { const { log, syscall } = buildSyscall(); function build(_vatPowers) { const ex1 = Far('export', {}); const root = Far('root', { - one() { + one(_arg) { return ex1; }, }); @@ -645,11 +647,12 @@ test('dropExports', async t => { const dispatch = makeDispatch(syscall, build, 'vatA', true); t.deepEqual(log, []); const rootA = 'o+0'; + const arg = 'o-1'; - // rp1 = root~.one() + // rp1 = root~.one(arg) // ex1 = await rp1 const rp1 = 'p-1'; - dispatch(makeMessage(rootA, 'one', capargs([]), rp1)); + dispatch(makeMessage(rootA, 'one', capargsOneSlot(arg), rp1)); await waitUntilQuiescent(); const l1 = log.shift(); const ex1 = l1.resolutions[0][2].slots[0]; @@ -659,9 +662,22 @@ test('dropExports', async t => { }); t.deepEqual(log, []); - // now tell the vat to drop that export - dispatch(makeDropExports(ex1)); + // now tell the vat we don't need a strong reference to that export // for now, all that we care about is that liveslots doesn't crash + dispatch(makeDropExports(ex1)); + + // and release its identity too + dispatch(makeRetireExports(ex1)); + + // Sending retireImport into a vat that hasn't yet emitted dropImport is + // rude, and would only happen if the exporter unilaterally revoked the + // object's identity. Normally the kernel would only send retireImport + // after receiving dropImport (and sending a dropExport into the exporter, + // and getting a retireExport from the exporter, gracefully terminating the + // object's identity). We do it the rude way because it's good enough to + // test that liveslots can tolerate it, but we may have to update this when + // we implement retireImport for real. + dispatch(makeRetireImports(arg)); }); // Create a WeakRef/FinalizationRegistry pair that can be manipulated for diff --git a/packages/SwingSet/test/util.js b/packages/SwingSet/test/util.js index fbb59b9ca8a..190ec8610c2 100644 --- a/packages/SwingSet/test/util.js +++ b/packages/SwingSet/test/util.js @@ -131,3 +131,13 @@ export function makeDropExports(...vrefs) { const vatDeliverObject = harden(['dropExports', vrefs]); return vatDeliverObject; } + +export function makeRetireExports(...vrefs) { + const vatDeliverObject = harden(['retireExports', vrefs]); + return vatDeliverObject; +} + +export function makeRetireImports(...vrefs) { + const vatDeliverObject = harden(['retireImports', vrefs]); + return vatDeliverObject; +}