If you change `racket-bitsyntax` to use `typed/racket/base/no-check`
for its `bitstring.rkt` module, this runs about 15x faster than the
`syndicate` version of the stack. Otherwise, it runs about 3x faster
than the `syndicate` version of the stack.
Instead of having restriction-paths as an adjunct to a change, they're
more propertly a part of each assertion itself. The new `skeleton.rkt`
keeps an optional restriction-path with each assertion, treating it as
distinct from its underlying assertion. The idea of not signalling
changes in assertions that have a restriction-path mismatch stays.
This is used when relaying: because we don't have access to the full
term, but only the projection results, we are inserting various
`(discard)`s. This is the cause of the failure in
`test/core/nesting-confusion.rkt`.
By adding `restriction-path`, we allow the inner dataspace to avoid
showing a reconstructed term to endpoints that might be able to
observe the reconstructed parts.
An alternative implementation approach is to generalize patterns in
the inner relay actor, translating all `(discard)`s into captures,
which would give us all the relevant terms that we need. The way I've
chosen to go (or at least, to try out) allows us to potentially keep
the "efficient" idea of just transmitting pattern-bound values across
some network link connecting dataspaces. The alternative would require
transmission of the full assertions, eliding no irrelevant detail.
It's also nicely fast compared to the old-Syndicate version :-)
There are still some problems with parameters in cross-ds relaying;
the symptom is #f for (current-facet) at some point, leading to some
sprites that don't get retracted (!).
The `add-endpoint!` call is changed in two ways:
- the old `assertion-fn` has become `update-fn`, yielding both
an assertion *and* an optional handler, because if the handler
depends on a field which changes, previously the handler wasn't
being updated
- a new parameter, `dynamic?`, can be set to #f (it's usually #t)
to ensure that the assertion and skeleton-interest are calculated
only once ever, and are not connected to the dataflow machinery.
The first change makes it possible for the `(later-than (deadline))`
pattern, where `deadline` is a field, to work; the second change makes
`during` and `during/spawn` work correctly in the face of field
updates.
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 8624047.
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.