Module time/sleep

time/sleep
Stability: unstable — `interval` landed 2026-09-10 (#532) and has had one release, and it ships exactly one missed-tick policy. `tick()` BURSTS to catch up, which is Tokio's `MissedTickBehavior::Burst` default and right for anything that counts ticks, but wrong for a rate limiter; Tokio's `Delay` and `Skip` are deliberately not implemented until a call site needs one, so the selector that would carry them (a constructor argument, or `set_missed_tick_behavior`) is an unmade decision in this module's surface. The other open item is the input contract the two `sleep` forms share: the millisecond floor and the negative-`Duration` hole above are properties of the code, not of any signature, and freezing them would freeze the hole. `sleep` / `sleep_blocking` themselves — the names, the split, and the rule that async code uses `sleep` — are settled. — stable modules only change additively; this one may still change.

Sleeping — one async form and one blocking form, both taking a Duration.

sleep suspends the calling async task and lets the event loop run other work; sleep_blocking parks the whole OS thread. Prefer sleep inside async code — sleep_blocking there would stall every other task on the loop.

The underlying timers take whole milliseconds, so sub-millisecond precision in the Duration is truncated — a Duration.from_micros(500) sleeps for no time at all. A NEGATIVE Duration is worse than that: both forms cast the millisecond count to an unsigned type, so it becomes a wait of ~585 million years rather than an immediate return (issues/stddoc-io-negative-duration-sleeps-forever.md). Pass a span you know to be non-negative until that is fixed.

interval is the third form: a repeating tick that does NOT drift, which a sleep in a loop does. See its own doc.

Example

{ sleep, sleep_blocking } :: import "std/time/sleep";
{ Duration } :: import "std/time/duration";

// Blocking: parks this thread for 100ms.
sleep_blocking(Duration.from_millis(i64(100)));

// Async: suspends this task for 100ms.
main :: (fn(io : Io) -> unit)({
  io.await(sleep(Duration.from_millis(i64(100)), io), io);
});

Stability

unstable — interval landed 2026-09-10 (#532) and has had one release, and it ships exactly one missed-tick policy. tick() BURSTS to catch up, which is Tokio's MissedTickBehavior::Burst default and right for anything that counts ticks, but wrong for a rate limiter; Tokio's Delay and Skip are deliberately not implemented until a call site needs one, so the selector that would carry them (a constructor argument, or set_missed_tick_behavior) is an unmade decision in this module's surface. The other open item is the input contract the two sleep forms share: the millisecond floor and the negative-Duration hole above are properties of the code, not of any signature, and freezing them would freeze the hole. sleep / sleep_blocking themselves — the names, the split, and the rule that async code uses sleep — are settled.

Types

Interval object
Interval

A repeating tick — Tokio's interval.

Why this exists rather than sleep in a loop. while(true, { work(); sleep(period); }) DRIFTS: each iteration takes period + work, so after 100 ticks of 10 ms work on a 1 s period the clock is 1 s behind. An interval anchors every tick to a SCHEDULE — tick n is due at start + n * period — so the work duration comes out of the wait instead of being added to it.

t := interval(Duration.from_millis(i64(100)), io);
io.await(t.tick(io), io);   // completes immediately
io.await(t.tick(io), io);   // completes 100ms after the FIRST tick

The first tick() completes immediately, as Tokio's does: an interval is almost always driving a loop whose first pass should not be delayed.

Fields

NameTypeDescription
_periodDuration
_nextInstant

When the next tick is DUE. Advanced by exactly one period per tick, so the schedule is never re-anchored to "now".

impl(Interval, ...)
tick : (Interval) fn(self : Interval, io : Io) -> Impl : (Future[Future](unit))

Wait until the next tick is due.

Missed ticks BURST. If the caller's work overran the period, _next is already in the past and this returns immediately, then advances by one period — so the ticks the stall swallowed are delivered back to back until the schedule is caught up. That is Tokio's default (MissedTickBehavior::Burst) and it is right for anything that counts ticks (sampling, accounting): the total number of ticks over an interval is what the period promises.

It is WRONG for a rate limiter, which wants the stall to swallow the ticks rather than repay them. That is Tokio's Delay/Skip, and it is deliberately not implemented until something needs it — the choice has to be made per call site and guessing it here would be worse than leaving it out.

Parameters

NameTypeNotes
selfInterval
ioIo

Returns: Impl : (Future[Future](unit))

period : (Interval) fn(self : Interval) -> Duration

The configured period.

Parameters

NameTypeNotes
selfInterval

Returns: Duration

Functions

sleep function
fn(duration : Duration, io : Io) -> Impl(Future(unit))

Suspend the current async task for duration, letting other tasks on the event loop run in the meantime.

Parameters

NameTypeNotes
durationDuration
ioIo

Returns: Impl(Future(unit))

sleep_blocking function
fn(duration : Duration) -> unit

Block the current thread for duration. Nothing else runs on this thread until it returns — inside async code use sleep instead.

Parameters

NameTypeNotes
durationDuration

Returns: unit

interval function
fn(period : Duration, io : Io) -> Interval

Start a repeating tick of period. The first tick() is immediate.

Parameters

NameTypeNotesDescription
periodDuration

The configured period.

ioIo

Returns: Interval