diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index 84335ae845c..4ffdb8e3d94 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -101,6 +101,17 @@ function build( const safetyPins = new Set(); // temporary const deadSet = new Set(); // vrefs that are finalized but not yet reported + function retainExportedRemotable(vref) { + // if the vref corresponds to a Remotable, keep a strong reference to it + // until the kernel tells us to release it + const { type, allocatedByVat, virtual } = parseVatSlot(vref); + if (type === 'object' && allocatedByVat && !virtual) { + const remotable = slotToVal.get(vref).deref(); + assert(remotable, X`somehow lost Remotable for ${vref}`); + exportedRemotables.add(remotable); + } + } + /* Imports are in one of 5 states: UNKNOWN, REACHABLE, UNREACHABLE, COLLECTED, FINALIZED. Note that there's no actual state machine with those @@ -395,11 +406,13 @@ function build( assert.equal(passStyleOf(val), 'remotable'); slot = exportPassByPresence(); } - parseVatSlot(slot); // assertion + const { type } = parseVatSlot(slot); // also used as assertion valToSlot.set(val, slot); slotToVal.set(slot, new WeakRef(val)); - // we do not use droppedRegistry for exports - exportedRemotables.add(val); // keep it alive until kernel tells us to release it + if (type === 'object') { + deadSet.delete(slot); + droppedRegistry.register(val, slot); + } } return valToSlot.get(val); } @@ -418,10 +431,10 @@ function build( const { type, virtual } = parseVatSlot(slot); slotToVal.set(slot, new WeakRef(val)); valToSlot.set(val, slot); - if (type === 'object' || type === 'device') { - // we don't dropImports on promises, to avoid interaction with retire - droppedRegistry.register(val, slot); + // we don't dropImports on promises, to avoid interaction with retire + if (type === 'object') { deadSet.delete(slot); // might have been FINALIZED before, no longer + droppedRegistry.register(val, slot); } // TODO: until #2724 is implemented, we cannot actually release @@ -521,6 +534,7 @@ function build( function collect(promiseID, rejected, value) { doneResolutions.add(promiseID); const valueSer = m.serialize(value); + valueSer.slots.map(retainExportedRemotable); resolutions.push([promiseID, rejected, valueSer]); scanSlots(valueSer.slots); } @@ -552,6 +566,7 @@ function build( } const serArgs = m.serialize(harden(args)); + serArgs.slots.map(retainExportedRemotable); const resultVPID = allocatePromiseID(); lsdebug(`Promise allocation ${forVatID}:${resultVPID} in queueMessage`); // create a Promise which callers follow for the result, give it a @@ -609,6 +624,7 @@ function build( } return (...args) => { const serArgs = m.serialize(harden(args)); + serArgs.slots.map(retainExportedRemotable); forbidPromises(serArgs); const ret = syscall.callNow(slot, prop, serArgs); insistCapData(ret); @@ -802,11 +818,15 @@ function build( // TODO: when we add notifyForward, guard against cycles function exitVat(completion) { - syscall.exit(false, m.serialize(harden(completion))); + const args = m.serialize(harden(completion)); + args.slots.map(retainExportedRemotable); + syscall.exit(false, args); } function exitVatWithFailure(reason) { - syscall.exit(true, m.serialize(harden(reason))); + const args = m.serialize(harden(reason)); + args.slots.map(retainExportedRemotable); + syscall.exit(true, args); } function disavow(presence) { @@ -858,8 +878,8 @@ function build( const rootSlot = makeVatSlot('object', true, BigInt(0)); valToSlot.set(rootObject, rootSlot); slotToVal.set(rootSlot, new WeakRef(rootObject)); + retainExportedRemotable(rootSlot); // we do not use droppedRegistry for exports - exportedRemotables.add(rootObject); } /**