Skip to content

Commit

Permalink
chore(swingset): update docs on liveslots' dispatch() function
Browse files Browse the repository at this point in the history
Chip's work on #2910 discovered that the supervisor was not told about
failures during liveslot's dispatch(). This could conceal some bugs in
liveslots, as well as hiding userspace-caused failures during
`buildRootObject()`.

The contract between `dispatch()` and the calling supervisor code has changed
through various bouts of refactoring, and it was ambiguous as to whether
`dispatch()` was supposed to protect against userspace errors or not. This
commit clears up the documentation to make this more explicit.
  • Loading branch information
warner authored and FUDCo committed Jan 22, 2022
1 parent e39ce77 commit ea819a4
Showing 1 changed file with 37 additions and 8 deletions.
45 changes: 37 additions & 8 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1202,12 +1202,39 @@ function build(
}

/**
* 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).
* This 'dispatch' function is the entry point for the vat as a whole: the
* vat-worker supervisor gives us VatDeliveryObjects (like
* dispatch.deliver) to execute. Here in liveslots, we invoke user-provided
* code during this time, which might cause us to make some syscalls. This
* userspace code might use Promises to add more turns to the ready promise
* queue, but we never give it direct access to the timer or IO queues
* (setImmediate, setInterval, setTimeout), so once the promise queue is
* empty, the vat userspace loses "agency" (the ability to initiate further
* execution), and waitUntilQuiescent fires. At that point we return
* control to the supervisor by resolving our return promise.
*
* Liveslots specifically guards against userspace reacquiring agency after
* our return promise is fired: vats are idle between cranks. Metering of
* the worker guards against userspace performing a synchronous infinite
* loop (`for (;;) {}`, the dreaded cthulu operator) or an async one
* (`function again() { return Promise.resolve().then(again); }`), by
* killing the vat after too much work. Userspace errors during delivery
* are expressed by calling syscall.resolve to reject the
* dispatch.deliver(result=) promise ID, which is unrelated to the Promise
* that `dispatch` returns.
*
* Liveslots does the authority to stall a crank indefinitely, by virtue of
* having access to `waitUntilQuiescent` and `FinalizationRegistry` (to
* retain agency), and the ability to disable metering (to disable worker
* termination), but only a buggy liveslots would do this. The kernel is
* vulnerable to a buggy liveslots never finishing a crank.
*
* This `dispatch` function always returns a Promise. It resolves (with
* nothing) when the crank completes successfully. If it rejects, that
* indicates the delivery has failed, and the worker should send an
* ["error", ..] `VatDeliveryResult` back to the kernel (which may elect to
* terminate the vat). Userspace should not be able to cause the delivery
* to fail: only a bug in liveslots should trigger a failure.
*
* @param { VatDeliveryObject } delivery
* @returns { Promise<void> }
Expand All @@ -1223,8 +1250,10 @@ function build(
// any promise it returns to fire.
const p = Promise.resolve(delivery).then(unmeteredDispatch);

// Instead, we wait for userspace to become idle by draining the promise
// queue.
// Instead, we wait for userspace to become idle by draining the
// promise queue. We return 'p' so that any bugs in liveslots that
// cause an error during unmeteredDispatch will be reported to the
// supervisor (but only after userspace is idle).
return gcTools.waitUntilQuiescent().then(() => p);
}
}
Expand Down

0 comments on commit ea819a4

Please sign in to comment.