Module sys/events
Event-loop handles — TTY modes, fd polling and filesystem watches.
The libuv-shaped part of the runtime: long-lived HANDLES that register a
callback with the event loop, rather than one-shot operations that return
an IoFuture. Both families here (poll_* and fs_event_*) follow the
same four-call lifecycle — init, start, stop, close — and both
keep the loop alive while started.
std/fs/watch.yo (Watcher) is the public surface over the fs-event
half, and std/sys/tty.yo re-exports the TTY_MODE_* constants for
std/term. The poll half has no public wrapper yet.
Stability
unstable — three of the flag constants below currently promise more than
any backend delivers: FS_EVENT_WATCH_ENTRY and FS_EVENT_STAT are read
by no platform at all, and FS_EVENT_RECURSIVE is honoured only on
Windows. The handles are also raw C pointers wrapped in one-field structs
with no ownership: using one after close is undefined and nothing
prevents it, and poll_init/fs_event_init have no error return so an
allocation failure is not observable. Freezing needs two things: recursion
delivered on every backend rather than only on Windows — the mirror image
of the Windows-shaped question std/fs/watch.yo's own Stability note
names — and handles that own their C allocation.
Types
Signature of a poll callback: (events, status, user_data), where
events is the OR of the POLL_* bits that fired and status is 0 or a
negative errno.
It runs on the EVENT-LOOP thread, from the loop's tick, so it must not block. Polling is level-triggered: a readable descriptor fires on every tick until it is drained.
A poll registration. handle is the runtime's own C allocation, not a
Yo-owned one — copying the struct copies the pointer, and poll_close
invalidates every copy.
Fields
| Name | Type | Description |
|---|---|---|
handle | *(u8) | The opaque runtime handle. Treat it as private. |
Signature of a filesystem-watch callback: (path, events, user_data).
path is the ENTRY NAME relative to the watched directory (the file's own
name when a file is watched), not a full path, and it is a borrowed C
string valid only for the duration of the call — copy it if you keep it.
events is FS_EVENT_RENAME and/or FS_EVENT_CHANGE. Like
PollCallback it runs on the event-loop thread.
A filesystem-watch registration, with the same "the pointer is the
runtime's" caveat as PollHandle.
Fields
| Name | Type | Description |
|---|---|---|
handle | *(u8) | The opaque runtime handle. Treat it as private. |
Functions
Allocate a poll handle bound to fd. Nothing is watched until
poll_start, and the descriptor is not validated here.
There is no error return, so an allocation failure is not observable at this layer — see the module's Stability note.
Parameters
| Name | Type | Notes |
|---|---|---|
fd | i32 |
Returns: PollHandle
Start watching for events (an OR of POLL_READABLE, POLL_WRITABLE
and POLL_PRIORITIZED — POLL_DISCONNECT is report-only). Returns 0 on
success, -EINVAL for a null callback.
user_data is handed back to the callback unchanged, which is how a
callback finds its owning object. While started, the handle keeps the
event loop ALIVE: a program whose only remaining work is a started poll
will not exit.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
handle | PollHandle | The opaque runtime handle. Treat it as private. | |
events | i32 | ||
callback | PollCallback | ||
user_data | *u8 |
Returns: i32
Stop watching and release the loop reference. Returns 0 on success,
-EINVAL for a null handle. Idempotent: stopping a handle that is
already stopped returns 0 without doing anything.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
handle | PollHandle | The opaque runtime handle. Treat it as private. |
Returns: i32
Stop (if needed) and FREE the handle. The PollHandle and every copy of
it dangle afterwards. It does not close the descriptor — that is the
caller's.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
handle | PollHandle | The opaque runtime handle. Treat it as private. |
Returns: unit
Allocate a filesystem-watch handle. No path is attached until
fs_event_start, and as with poll_init there is no error return.
Returns: FsEventHandle
Start watching path — a directory or a single file. Returns 0 on
success, a negative errno on failure (-EINVAL for a null path or
callback, -ENOENT for a missing path, -ENOTSUP on wasm, which has no
filesystem events).
flags is FS_EVENT_RECURSIVE or 0; see that constant for which
platforms honour it. user_data is passed back to the callback. While
started, the handle keeps the event loop alive.
The backends are genuinely different mechanisms, which is why event
delivery is coarse: inotify on Linux (mask IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_ATTRIB), a kqueue
EVFILT_VNODE watch on macOS backed by a directory SNAPSHOT that the
loop diffs to work out which entry changed, and ReadDirectoryChangesW
on Windows.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
handle | FsEventHandle | The opaque runtime handle. Treat it as private. | |
path | *u8 | ||
flags | u32 | ||
callback | FsEventCallback | ||
user_data | *u8 |
Returns: i32
Stop watching and release the loop reference, tearing down the platform
watch (the inotify watch, the kqueue and its snapshot, the directory
handle). Returns 0 on success, -EINVAL for a null handle; idempotent.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
handle | FsEventHandle | The opaque runtime handle. Treat it as private. |
Returns: i32
Stop (if needed) and FREE the handle. Every copy dangles afterwards.
Calling this is what stops the loop calling back into memory the owner has
dropped — std/fs/watch's Watcher does it from Dispose for exactly
that reason.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
handle | FsEventHandle | The opaque runtime handle. Treat it as private. |
Returns: unit
Constants
UV_TTY_MODE_NORMAL — restore the mode that std/sys/tty's tty_init
saved: echo on, line-buffered, Ctrl-C generates a signal.
Value: 0
UV_TTY_MODE_RAW — no echo, no line buffering, no signal generation, so
each keystroke is readable immediately and Ctrl-C arrives as a byte. What
a full-screen or key-at-a-time UI wants.
Value: 1
UV_TTY_MODE_IO — binary mode: output post-processing and input
translation off, but echo and canonical input left as they are. The
setting for moving bytes through a terminal without newline mangling.
Value: 2
UV_READABLE — ask to be told when the descriptor has data (POLLIN).
Value: 1
UV_WRITABLE — ask to be told when the descriptor can accept data
(POLLOUT).
Value: 2
UV_DISCONNECT — the peer hung up (POLLHUP). Report-only: it is
delivered to the callback but cannot be REQUESTED, because poll(2)
reports POLLHUP in revents regardless of what was asked for. Passing
it to poll_start has no effect.
Value: 4
UV_PRIORITIZED — out-of-band / priority data is available
(POLLPRI); on a TCP socket, urgent data.
Value: 8
UV_FS_EVENT_WATCH_ENTRY — accepted for libuv parity and read by NO
backend in this tree. Passing it changes nothing.
Value: 1
UV_FS_EVENT_STAT — accepted for libuv parity and read by NO backend in
this tree. Passing it changes nothing.
Value: 2
UV_FS_EVENT_RECURSIVE — watch the whole subtree, not just the directory
itself. Honoured on Windows ONLY, where it becomes
ReadDirectoryChangesW's bWatchSubtree. Linux adds IN_ISDIR to the
inotify mask, which does not make inotify recurse, and macOS ignores it —
so on both, a recursive watch has to be built by watching each directory.
Value: 4
The events bit meaning an entry was created, removed or moved — libuv's
"rename" class. Backends cannot always tell the three apart, which is why
they share one bit.
Value: 1
The events bit meaning an entry's contents or metadata changed. A single
write may surface as one or several of these depending on how the platform
coalesces.
Value: 2