Cancelling and steering runs
Nothing kills your node
A cancelled run trips a flag. Your node is what acts on it. If your body never looks, nothing stops it, and the only sign is a run that never ends.
So anything that waits needs a way out:
let cancel = ctx.cancellation();
tokio::select! {
result = do_the_long_thing() => result?,
_ = cancel.cancelled() => return Ok(()),
}
For a loop, the cheap synchronous read:
for row in rows {
if ctx.is_cancelled() { return Ok(()); }
...
}
That one is cheap enough to call every iteration.
The flag is sticky. Once it is set, every later check sees it, so there is no race between the cancel arriving and your next look.
If your node waits on something itself (a sleep, a long call to somebody
else, a read that could block for minutes), race it against
cancel.cancelled() in a select! like the one above. The waits weft parks
for you need none: ctx.await_signal, ctx.await_signal_with_cancel, and the
ctx calls that wait through them, like infra(..).start(). Cancelling the run
takes those down for you.
If the wait is on a job a provider runs, and you want the provider told too, go and read if the run is cancelled while the job runs.
Stopping other runs
A run can tag itself, and a run can stop every run carrying a tag.
ctx.tag_run(["chat_7"]).await?;
ctx.stop_tagged("chat_7", StopSelf::Keep).await?;
That is the debounce shape. Somebody sends three messages in a row; the third run stops the first two and answers once.
| What it does | |
|---|---|
StopSelf::Keep | Stop the others, keep running |
StopSelf::Include | Stop everything carrying the tag, including this run |
It reaches runs that are working, ones parked on a signal or a timer, whose wake is erased so they never come back, and ones whose wake is already on its way, which find the run gone and do nothing. Each one is recorded as cancelled, naming this run and the tag, so it reads as a decision rather than a failure.
It never crosses a project.
Tag first, then stop
StopSelf::Keep only reaches runs that took the tag before this one did.
That is what stops two runs killing each other. Two messages arriving milliseconds apart both say “stop the others, keep me”, and because the order is by when each tagged itself, the later one survives and the earlier one stops. Without that rule you would get both dead, or neither.
So tag at the top of your node, then stop. A node that stops before it tags is not in the order yet.
The stop is not immediate
stop_tagged returns when the request is durably queued, not when the other
runs are gone. Do not write a node whose next line assumes they stopped.
A tag is [A-Za-z0-9_-], one to sixty-four characters, but you can hand
either call any string that is not empty. If you pass a chat id or an email
address, weft keeps the readable part, swaps every other character for _,
and adds a short fingerprint of the original, so 49151@s.whatsapp.net
becomes 49151_s_whatsapp_net- followed by sixteen hex characters. Both calls
and the runs list’s tag filter do this the same way, so the same value always
lands on the same tag, and two values that only differ in the swapped
characters never share one. Tagging twice with the same tag does nothing,
which is what makes it safe to replay.
In a test
The fake rig does not stop anything. It records what your node asked for, and you assert on that:
assert_eq!(rig.run_tags(), vec![vec!["chat_7"]]);
assert_eq!(rig.stops().len(), 1);
Taking down what your own run uses
StopSelf is also the last argument of every call that takes part of the
project down (ctx.infra("bridge").stop(..), terminate(..),
wipe(..), upgrade(..), ctx.trigger("receive").deactivate(..),
ctx.runs().clean(..)). For what
Keep and Include do there, go and read
taking something down from a run.
In a test, rig.program_calls() lists every such call with the StopSelf it
passed. If your node reads something back, such as an infra copy’s status,
rig.answer_program_call("weft.infra.status", ..) sets what that call returns
in the test.