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.