Skip to content

Commit

Permalink
fix(liveslots): low-level vat dispatch() is now async
Browse files Browse the repository at this point in the history
`dispatch()`, the low-level interface to each vat (generally provided by
liveslots), is now async. Vats are responsible for not resolving the promise
returned by `dispatch()` until the user-level code has finished running and
the crank is complete. Vats are given `waitUntilQuiescent` in their `gcTools`
argument to facilitate this.

This will make it possible for liveslots to run `gc()` and wait long enough
to give finalizers a chance to run (and then call `dropImports`) before the
crank is considered complete.

closes #2671
refs #2660
  • Loading branch information
warner committed Apr 26, 2021
1 parent fa24bb9 commit 1a6ae48
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 108 deletions.
39 changes: 34 additions & 5 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const DEFAULT_VIRTUAL_OBJECT_CACHE_SIZE = 3; // XXX ridiculously small value to
* @param {boolean} enableDisavow
* @param {*} vatPowers
* @param {*} vatParameters
* @param {*} gcTools { WeakRef, FinalizationRegistry, vatDecref }
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
* @param {Console} console
* @returns {*} { vatGlobals, dispatch, setBuildRootObject }
*
Expand All @@ -45,7 +45,7 @@ function build(
gcTools,
console,
) {
const { WeakRef, FinalizationRegistry } = gcTools;
const { WeakRef, FinalizationRegistry, waitUntilQuiescent } = gcTools;
const enableLSDebug = false;
function lsdebug(...args) {
if (enableLSDebug) {
Expand Down Expand Up @@ -816,8 +816,12 @@ function build(
exported.add(rootObject);
}

function dispatch(vatDeliveryObject) {
const [type, ...args] = vatDeliveryObject;
/**
* @param { VatDeliveryObject } delivery
* @returns { void }
*/
function dispatchToUserspace(delivery) {
const [type, ...args] = delivery;
switch (type) {
case 'message': {
const [targetSlot, msg] = args;
Expand Down Expand Up @@ -849,6 +853,31 @@ function build(
assert.fail(X`unknown delivery type ${type}`);
}
}

/**
* This low-level liveslots code is responsible for deciding when userspace
* is done with a crank. Userspace code can use Promises, so it can add as
* much as it wants to the ready promise queue. But since userspace never
* gets direct access to the timer or IO queues (i.e. setImmediate,
* setInterval, setTimeout), then once the promise queue is empty, the vat
* has lost "agency" (the ability to initiate further execution).
*
* @param { VatDeliveryObject } delivery
* @returns { Promise<void> }
*/
async function dispatch(delivery) {
// Start user code running, record any internal liveslots errors. We do
// *not* directly wait for the userspace function to complete, nor for
// any promise it returns to fire.
Promise.resolve(delivery)
.then(dispatchToUserspace)
.catch(err =>
console.log(`liveslots error ${err} during delivery ${delivery}`),
);
// Instead, we wait for userspace to become idle by draining the promise
// queue.
return waitUntilQuiescent();
}
harden(dispatch);

// we return 'deadSet' for unit tests
Expand All @@ -865,7 +894,7 @@ function build(
* @param {*} vatParameters
* @param {number} cacheSize Upper bound on virtual object cache size
* @param {boolean} enableDisavow
* @param {*} gcTools { WeakRef, FinalizationRegistry }
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
* @param {Console} [liveSlotsConsole]
* @returns {*} { vatGlobals, dispatch, setBuildRootObject }
*
Expand Down
17 changes: 10 additions & 7 deletions packages/SwingSet/src/kernel/vatManager/supervisor-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ function makeSupervisorDispatch(dispatch, waitUntilQuiescent) {
*
*/
async function dispatchToVat(delivery) {
// the (low-level) vat is not currently responsible for giving up agency:
// we enforce that ourselves for now
Promise.resolve(delivery)
// the (low-level) vat is responsible for giving up agency, but we still
// protect against exceptions
return Promise.resolve(delivery)
.then(dispatch)
.catch(err =>
console.log(`error ${err} during vat dispatch of ${delivery}`),
.then(
() => harden(['ok', null, null]),
err => {
// TODO react more thoughtfully, maybe terminate the vat
console.log(`error ${err} during vat dispatch of ${delivery}`);
return harden(['error', `${err.message}`, null]);
},
);
await waitUntilQuiescent();
return harden(['ok', null, null]);
}

return harden(dispatchToVat);
Expand Down
3 changes: 2 additions & 1 deletion packages/SwingSet/test/liveslots-helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { WeakRef, FinalizationRegistry } from '../src/weakref';
import { waitUntilQuiescent } from '../src/waitUntilQuiescent';
import { makeLiveSlots } from '../src/kernel/liveSlots';

export function buildSyscall() {
Expand Down Expand Up @@ -37,7 +38,7 @@ export function makeDispatch(
vatID = 'vatA',
enableDisavow = false,
) {
const gcTools = harden({ WeakRef, FinalizationRegistry });
const gcTools = harden({ WeakRef, FinalizationRegistry, waitUntilQuiescent });
const { setBuildRootObject, dispatch } = makeLiveSlots(
syscall,
vatID,
Expand Down
Loading

0 comments on commit 1a6ae48

Please sign in to comment.