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

Share one deadline across waits

A timeout bounds one wait. When a task makes several waits, give it a Deadline from std.time: one total budget that every wait draws from.

import std.sync
from std.time import Deadline, monotonic_now, deadline_after, milliseconds

def collect(inbox: &Receiver[i64], deadline: &Deadline, cancel: &CancellationToken) -> Result[i64, OverflowError[i64]]:
    mut total = 0
    while True:
        match std.sync.recv_before(inbox, deadline, cancel):
            case Ok(value):
                total += value
            case Err(std.sync.WaitError.TimedOut):
                break
            case Err(std.sync.WaitError.Disconnected) | Err(std.sync.WaitError.Cancelled):
                break
    Ok(total)

def main() -> Result[(), AllocError | IoError | RangeError | SendError[i64] | OverflowError[i64]]:
    sender, receiver = channel[i64](4)?
    sender.send(1)?
    sender.send(2)?
    sender.send(3)?
    deadline = deadline_after(&monotonic_now(), &milliseconds(20))?
    cancel = CancellationToken()?
    print(collect(&receiver, &deadline, &cancel)?)?
    print(deadline.expired(&monotonic_now()))?
    print(deadline.remaining(&monotonic_now()).as_timeout_ms())?
    Ok(())
6
True
0

recv_before checks caller cancellation between bounded waits and shares one deadline. The loop makes four waits against one 20-millisecond budget instead of granting each wait its own. remaining takes the current instant and returns zero once the deadline has passed. A zero budget still receives a message that is already queued, so the three sent values always arrive.

as_timeout_ms() is available for the low-level _timeout_ms methods. It converts a span to the u64 milliseconds that timed waits accept. It rounds up: a wait never returns before its deadline, and a remainder below a millisecond does not turn the loop into a zero-timeout spin.

Sleep that can be cancelled

sleep cannot be interrupted. To pause until either a span passes or someone asks you to stop, wait on a cancellation token with the span as the budget:

from std.time import seconds

def pause(stop: &CancellationToken) -> bool:
    stop.wait_timeout_ms(seconds(3600).as_timeout_ms())

def main() -> Result[(), AllocError | IoError | SpawnError[()]]:
    stop = CancellationToken()?
    with spawn(pause, &stop)? as task:
        stop.cancel()
        print(task.join())?
    Ok(())
True

The worker borrows the token and asks for an hour. It returns as soon as cancel() runs, even if the request comes before the wait starts. The result is true when cancellation ended the wait and false when the span elapsed.

Deadlines use the monotonic clock, so changing the system time neither extends nor shortens them. They limit waiting, not execution: a wait can return late on a busy machine. See the time reference.