Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Weft The book

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::KeepStop the others, keep running
StopSelf::IncludeStop 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.