Skip to content

std/timer ​

One-shot timers, repeating tickers, and timeouts.

milo
from "std/timer" import { Timer, Ticker, recvTimeout, waitReadable, waitWritable }

Everything here is a thin layer over machinery that already exists: a green task (std/runtime), a Channel (std/sync), and a Select timer arm (std/select). A Timer or Ticker is a task that sleeps and sends, so it composes with selectRecv like any other channel.

Scheduler ​

Timer and fd arms are driven by the green scheduler's poll loop. std/select documents the trap: on a program that never spawned a green task there is no scheduler, so sel.onTimeout is armed and never fires — it looks like protection and isn't. Every entry point in std/timer calls ensureTimersLive() first, so a timeout here is honest on the main context too.

Timer ​

The clock starts at Timer.after, not at the first recv.

milo
var t = Timer.after(Duration.secs(5))
if t.recv().isSome() {
    print("deadline reached")
}

Ticker ​

A slow receiver does not pile up a backlog of ticks; stop() takes effect at the ticker's next wake.

milo
var tk = Ticker.every(Duration.millis(100))
var sel = Select.new()
selectRecv(sel, tk.channel())
selectRecv(sel, work)
match sel.wait() {
    0 => { print("tick") }
    1 => { /* work arrived */ }
}
tk.stop()

Timeouts ​

recvTimeout is a free function, not a method, for the same reason selectRecv is one. waitReadable and waitWritable are the timeout under a blocking read or write: check first, then do the IO knowing it will not park forever.

milo
if !waitReadable(conn, Duration.secs(10)) {
    print("client idle, closing")
}

API reference ​

Ticker ​

milo
pub struct Ticker

A periodic timer. Dropping it stops it.

Ticker.channel ​

milo
fn Ticker.channel(self: &Ticker): Channel<Instant>

The tick channel, for arming in a Select.

Ticker.every ​

milo
fn Ticker.every(period: &Duration): Ticker

Deliver an Instant every period. A slow receiver does not accumulate a backlog: the buffer holds one tick and the sender drops any it can't deposit, which is Go's behavior and the only one that can't grow without bound.

A period of zero or less would be a hot loop, so it yields a Ticker that never fires.

Ticker.recv ​

milo
fn Ticker.recv(self: &Ticker): Option<Instant>

Block for the next tick. None once the ticker is stopped and drained.

Ticker.stop ​

milo
fn Ticker.stop(self: &Ticker): void

Stop ticking. The task exits at its next wake, so up to one period later. Idempotent.

Timer ​

milo
pub struct Timer

A one-shot timer. Dropping it cancels it: otherwise an unfired timer's task would sleep out the full span and send into a channel nobody holds.

Timer.after ​

milo
fn Timer.after(d: &Duration): Timer

Start a timer that delivers one Instant after d. The clock starts now, not at the first recv.

Timer.channel ​

milo
fn Timer.channel(self: &Timer): Channel<Instant>

The delivery channel, for arming in a Select alongside other sources.

Timer.recv ​

milo
fn Timer.recv(self: &Timer): Option<Instant>

Block until the timer fires. None if it was stopped first.

Timer.stop ​

milo
fn Timer.stop(self: &Timer): void

Cancel. Idempotent; a timer that already fired is unaffected (its value is still waiting in the channel).

Functions ​

recvTimeout ​

milo
pub fn recvTimeout<T>(ch: &Channel<T>, d: &Duration): Option<T>

Receive from ch, giving up after d. None means the timeout won or the channel closed empty.

A free function, not a method: Channel lives in std/sync, and an impl Channel<T> here would be visible only to programs that already import something from std/timer. A free function names its dependency.

waitReadable ​

milo
pub fn waitReadable<T: AsFd>(h: &T, d: &Duration): bool

Wait until h is readable, or d elapses. False on timeout.

This is the timeout under a blocking read: check first, then read knowing the read won't park forever. It reads nothing itself.

waitWritable ​

milo
pub fn waitWritable<T: AsFd>(h: &T, d: &Duration): bool

Wait until h accepts a write without blocking, or d elapses.