We now explicitly track *committed* assertions of each actor in a new
field, `actor-cleanup-changes`. Each time a patch action is
*performed*, `actor-cleanup-changes` is updated. When an actor quits,
it enqueues a special new kind of action, a `quit` action.
When a `quit` action is performed, any remaining contents of
`actor-cleanup-changes` are processed in order to fully remove any
leftover assertions. (Leftover assertions will only arise in
exceptional cases: when some stop-script or facet boot-script raises
an uncaught exception.)
As part of this commit, we undo the effect of commit b207a07.
This repairs a bug regarding crashes in a new actor's boot-proc.
Previously, if boot-proc raised an exception, the initial assertions
would stick around forever. By changing adhoc-assertions to a bag
rather than a set, and putting the initial assertions in the bag, we
put them somewhere they are guaranteed to be processed during actor
termination, even when an exception is signalled during boot.
This is an API change wrt the previous Syndicate implementation:
assert!/retract! now have bag semantics, not set semantics. We can add
set-semantics APIs if we end up needing them, of course, layered on
top of the bag implementation.
The patch here removes a terminated facet from its parent's
`facet-children` set only in a script, and only after all other
scripts enqueued as part of facet termination have executed without an
uncaught exception.
This means that, if (say) a stop script raises an uncaught exception,
it might have happened after some *but not all* scripts resulting from
calls to `retract-facet-assertions-and-subscriptions!` have already
executed. So some endpoints' assertions and subscriptions will have
been removed.
When the uncaught exception is caught by the handler in
`with-current-facet`, a call to `abandon-queued-work!` is made, which
discards queued scripts, including the remaining assertion-cleanup
scripts as well as the scripts for removing dead facets from their
parents' `facet-children` sets. It also (crucially) discards queued
patch actions, including those resulting from already-executed
assertion-cleanup scripts.
At this point, we have a facet tree with some dead facets still in it,
and no queued outbound patches. The assertions for the still-present
dead facets are still logically asserted.
Then, a call to `terminate-actor!` happens, which traverses the whole
tree enqueueing assertion-cleanup scripts. No user code is enqueued,
so (in principle) no exceptions can be signalled.
Once these `terminate-actor!`-enqueued scripts execute, a pending
patch exists that will remove all remaining endpoint assertions.
The remaining sticky point is the calls to `dataspace-unsubscribe!`.
Happily, these are idempotent because of the implementation in
`skeleton.rkt`.
Prior to this patch, terminating facets were removed early from their
parents' `facet-children` sets, meaning there was no way to find them
again to clean up if a failure occurred during a stop script.
Ideally, it'd be easy to see that the code is correct in this respect.
We're not there yet.