`PanicGuard` used to own the memo so that, in the case of panic,
we could reinstall the old value -- but there's no reason for us to
do that. It's just as good to clear the slot in that case and recompute
it later. Also, it makes the code nicer to remove it, since
it allows us to have more precision about where we know the memo is
not null.
My motivation though is to work towards "partial cycle recovery".
We need a clean and easy way to cancel the ongoing execution and reset
the slot to "not computed" (turns out we used that in
`maybe_changed_since` too!).
We still record the same dependencies (or else the tests fail,
so +1 for test coverage).
This has the immediate advantage that we don't invoke the fallback
function twice for the repeated node in the cycle.
Also, fix a bug where revalidating cycles could lead to a
CycleParticipant error that is not caught (added a test for it).
We used to store a changed-at/durability that reflected only
the current frame in a cycle -- but really we are dependent
across the entire cycle, so we now store the max changed-at and
min durability from the entire thing.
It turns out this is necessary, as this test reveals!
If we don't panic, you might encounter further cycles
that aren't supposed to have executed. (Prior to these changes,
this test was panicking from the second cycle.)
`QueryRevisions` is a shared struct that contains
"everything a query read thus far" -- it is roughly
the old `MemoRevisions` but without `verified_at`.
This change is useful because when a cycle occurs it
allows us to isolate out "inputs thus far" more easily.
The `Cancelled` struct now reflects multiple potential reasons
for a query execution to be cancelled, including unexpected cycles.
It also gives information about the participants that lacked
recovery information.
Because it bugs me to clone the vector.
Maybe silly, I admit, since cycle recovery
is not the hot path.
But by that same token, we now spend only 1 word
for a null pointer instead of 4 words for a (usually empty) vector.