std::clock
What time it is, and waiting for it to be later.
Its own module, and it did not start as one. Clock lived in
std::env because both answer “what did the outside world hand this
process”, and that grouping cost something the moment anything else wanted
a clock: env_native.kh is native-only for getenv and argv, so the
clock was native-only too – not because clocks are unportable, but because
of the file it was in. A Worker has Date.now. Splitting the two leaves one
small file with one reason to carry _native, and a clock_wasm.kh is
then an afternoon rather than an untangling.
It also puts the clock where the things that need one can import it:
std::resilience backs off, and every future timeout and deadline is the
same dependency. Importing std::env to get a clock was a line nobody
could explain.
The C side is not ISO C, and that is why these are runtime exports rather
than extern bindings to libc. Milliseconds are
GetSystemTimeAsFileTime on Windows and clock_gettime on Unix – two
calls, two epochs, two headers – and ISO C’s time is whole seconds with
nothing finer spelled the same everywhere. The runtime already knows which
target it was built for.
API at a glance
Section titled “API at a glance”Effects: Clock
Methods: Clock
Effects
Section titled “Effects”pub effect Clock { unix_seconds: () -> Int, unix_millis: () -> Int, monotonic_millis: () -> Int, sleep: (Int) -> (),}What time it is.
A capability rather than plain functions, and separate from Env because
it is a different thing to deny: a library that may read a configuration
variable is not thereby a library that may know how long something took,
and a test that pins one usually wants to pin the other differently.
Two clocks, not one, and the split is not a detail. A wall clock says what time it is and can jump: NTP steps it, an administrator sets it, a virtual machine resumes with a stale one – and it can jump backwards, so subtracting two readings of it is not a duration, it is a guess. A monotonic clock cannot go backwards and is what “how long did this take” actually wants; its zero is arbitrary, so it can never be a timestamp.
One millis would therefore have been the wrong one about half the time,
and the half it was wrong for would be the half that only fails in
production, at 2am, when the clock is stepped an hour. Naming both makes
the caller say which they meant.
unix_seconds
Section titled “unix_seconds”unix_seconds: () -> Int
Seconds since 1970, on the wall clock.
Kept, because it is what a caller who wants a date wants, and it is
derived from the same source as unix_millis so the two can never
disagree about what second it is.
unix_millis
Section titled “unix_millis”unix_millis: () -> Int
Milliseconds since 1970, on the wall clock. A timestamp, not a duration.
monotonic_millis
Section titled “monotonic_millis”monotonic_millis: () -> Int
Milliseconds on a clock that only goes forwards, counted from the first time anything in the process asked.
The one to subtract. Two readings are always ordered and their difference
is always a real elapsed time — which is exactly what unix_millis
cannot promise.
sleep: (Int) ->()
Waits this many milliseconds before going on.
An operation on the clock, and that is the whole design. Waiting is the one thing a program does that a test cannot afford to actually do, and putting it here means a fake clock is an ordinary handler – no test runtime, no special mode, no rule about which fiber may advance time. The alternative is what every language with an ambient sleep ends up building: a parallel clock the runtime knows about, and documentation that has to open by warning you to fork the sleeping code or deadlock.
The capability is the seam, which is the argument Random::seeded
already makes about the other unrepeatable input.
Zero or less returns at once. A caller who meant “let somebody else run”
wants a safepoint, and every loop back-edge already has one — and every
back-edge is a cancellation point too, so a loop { sleep(short) }
worker stops when it is asked to.
A sleep is a cancellation point. A fiber inside sleep is woken by a
cancellation on either fiber backend and stops there; the statement after
the sleep does not run.
Methods
Section titled “Methods”impl Clockpub fn real() -> ClockThe real clock.
Seconds are milliseconds divided down rather than a second call to ISO C’s
time, so that a program reading both never sees them straddle a tick.
Khora 0.4.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource