.time.* — clock and timers¶
Monotonic clock access plus a recurring-callback scheduler. Timers integrate into the runtime's poll loop — they only fire when control is back in ray_poll_run, i.e. between REPL inputs, between IPC requests, or while idling in server-only mode. A long-running synchronous evaluation will defer pending timers until it returns.
The clock is the same CLOCK_MONOTONIC source the scheduler uses for its deadlines, so (.time.now) is directly comparable to the ms argument passed to .time.timer.set. Wall-clock time is intentionally not exposed here: a host sleep / resume must not retroactively shift a scheduled deadline.
Restricted under -U
.time.timer.set and .time.timer.del are RAY_FN_RESTRICTED — installing callbacks on the server's poll loop is privileged. .time.now is unrestricted (it's a clock read).
Reference¶
| Function | Arity | Flags | Description |
|---|---|---|---|
.time.now |
variadic | — | Current monotonic time in milliseconds. |
.time.timer.set |
variadic | restricted | Schedule a callback; return its timer id. |
.time.timer.del |
unary | restricted | Cancel a scheduled timer (idempotent). |
.time.now¶
Signature: (.time.now). Returns an i64 count of milliseconds since some monotonic epoch (platform-defined; comparable only against itself and against timer deadlines on the same process).
Errors: domain if called with any arguments.
.time.timer.set¶
Signature: (.time.timer.set ms num fn).
ms—i64≥ 0. The delay until the first fire and the interval between subsequent fires.num—i64≥ 0.0means fire forever;N ≥ 1means fire exactly N times.fn— aRAY_LAMBDAwhose declared parameter list has exactly one entry. The callback is invoked with the current monotonic time (the same value.time.nowwould return).
Returns the new timer's i64 id. IDs are monotonically increasing per runtime — never reused after deletion.
Errors:
domain— arity not 3, negativems, negativenum, lambda with arity ≠ 1, or no poll loop active.type— non-i64ms/num, orfnnot a lambda.oom— heap allocation failed.
The callback's return value is released and discarded — timers are side-effecting procedures. If the callback raises an error, the error is printed to stderr (prefixed timer <id>:) and the timer continues as if the call had succeeded; one fire is still counted toward num.
;; Print "tick" every second, three times
(set id (.time.timer.set 1000 3 (fn [t] (println "tick @" t))))
;; Forever (num=0)
(set heartbeat
(.time.timer.set 5000 0
(fn [t] (println "heartbeat"))))
.time.timer.del¶
Signature: (.time.timer.del id). Removes the timer with that id from the heap and releases its retained lambda. Returns null. Idempotent — passing an unknown id is not an error.
Errors: type if id isn't an i64.
(.time.timer.del id) ;; cancel a running timer
(.time.timer.del id) ;; second call: null, not an error
(.time.timer.del 999999) ;; unknown id: null
Notes on the scheduler¶
- The min-heap is per
ray_poll_t; one timer set per runtime. - Ties on
exp_msare broken byid(lower id fires first). - The heap is allocated lazily on the first
.time.timer.setcall — callers that never schedule pay nothing. - On runtime shutdown the heap is walked and every retained lambda is released.
- There's no introspection (no
list-timers, nopause/resume) — design choice; track ids yourself if you need to manage many.