Skip to content

Reference index

15. Polling and pollers

This category covers the entry points for waiting on raw sockets, file descriptors, and generic timers in one event loop: the one-shot zlink_poll and the reusable poller family. Poller readiness is one of Core's three event families (the others are socket monitor events and timer fires — Socket monitor and Timers categories). The exact signatures are owned by the Polling specification.


Waits once on a caller-provided array of poll items.

zlink_pollitem_t items[2] = {
    { .socket = s1, .events = ZLINK_POLLIN },
    { .socket = s2, .events = ZLINK_POLLIN | ZLINK_POLLOUT },
};
zlink_config_result_t err;
int ready = zlink_poll(items, 2, /*timeout_ms=*/1000, &err);

Parameters. items is an array of zlink_pollitem_t (socket/fd/events/revents). item_count is the array length. timeout_ms is -1 for infinite, 0 to return immediately. error_out receives the failure detail.

Return and errno. Returns the number of ready items, 0 on timeout, or -1 on failure (with both error_out and errno set).

When to use. Use this for a single wait over a small, fixed set of items — every item's revents is cleared before evaluation, so it is only a snapshot immediately after return. Use the reusable poller family below instead when the item set changes often or you need fd/timer sources, since re-scanning an array each call doesn't scale as well as an incrementally maintained poller.


Creates a reusable poller, destroys it, or reads how many sources it currently holds.

void *poller = zlink_poller_new();
// ...
zlink_config_result_t err;
int n = zlink_poller_size(poller, &err);
// ...
zlink_poller_destroy(&poller);

Parameters. new takes no arguments. destroy takes void **poller_p (may clear the handle). size takes the poller and an error_out output.

Return and errno. new returns a poller handle or NULL. destroy returns zlink_close_result_t — destroying while a wait is active on it returns ZLINK_CLOSE_BUSY with EBUSY. size returns the count or -1 on failure.

When to use. One poller instance handles as many sources as needed and is reused across wait calls; different pollers may be used concurrently, but the caller must serialize add/modify/remove/wait on any one poller.


Registers, updates, or removes a raw socket source on a poller.

zlink_poller_add(poller, s, userdata, ZLINK_POLLIN);
zlink_poller_modify(poller, s, ZLINK_POLLIN | ZLINK_POLLOUT);
zlink_poller_remove(poller, s);

Parameters. source is the socket handle. user_data (add only) is the borrowed pointer delivered back in matching zlink_poller_event_t entries. events is the same zlink_poller_event_mask_t bits as zlink_pollitem_t, plus ZLINK_POLLCOMPLETION (valid only when adding a raw PAIR, DEALER, ROUTER, or STREAM that owns a completion queue — see below).

Return and errno. All three return zlink_config_result_tZLINK_CONFIG_OK on success. Adding an already-registered source returns ZLINK_CONFIG_CONFLICT with EEXIST. Modifying or removing a missing source returns ZLINK_CONFIG_NOT_FOUND with ENOENT. An invalid event bit returns ZLINK_CONFIG_INVALID_ARGUMENT with EINVAL; an event unsupported by the source returns ZLINK_CONFIG_NOT_SUPPORTED with ENOTSUP.

When to use. A poller only borrows the source handle — remove a source before destroying it. Set ZLINK_POLLCOMPLETION (alone, or OR-ed with ZLINK_POLLIN/ZLINK_POLLOUT) to let one poller own DATA readiness and completion readiness for the same socket together. DEALER and ROUTER use it for REQUEST results (DEALER/ROUTER categories); PAIR, DEALER, ROUTER, and STREAM also use it for pending-send completion records. The registration owns readiness only: zlink_poller_wait() does not remove records from the queue. Using it on any other source, in a zlink_poll item, or in zlink_poller_modify returns ZLINK_CONFIG_INVALID_ARGUMENT/EINVAL. A registered source that closes produces POLLERR once and stays registered until explicitly removed.


Registers, updates, or removes a raw platform file-descriptor source on a poller.

zlink_poller_add_fd(poller, fd, userdata, ZLINK_POLLIN);

Parameters. fd is a zlink_fd_t (platform file descriptor/handle). Otherwise identical in shape to zlink_poller_add/_modify/_remove.

Return and errno. Same as the socket-source trio above — ZLINK_CONFIG_CONFLICT/EEXIST on duplicate add, ZLINK_CONFIG_NOT_FOUND/ENOENT on a missing source.

When to use. Use this to fold a plain OS file descriptor into the same event loop as sockets and timers — readiness follows platform poll semantics (readable/writable), not the raw-socket readiness rules.


Registers or removes a generic timer source (Timers category) on a poller.

zlink_poller_add_timer(poller, timer, userdata);
zlink_poller_remove_timer(poller, timer);

Parameters. timer is a handle from zlink_timer_new (Timers category). No events mask — a timer source only ever signals POLLIN (a fire count is available).

Return and errno. Both return zlink_config_result_t — the same conflict/not-found mapping as the other two source families.

When to use. Use this to receive timer fires through the same wait loop as sockets and FDs, draining the accumulated count with zlink_timer_recv (Timers category) once wait reports it.


Waits for readiness across every source currently registered on a poller.

zlink_poller_event_t events[16];
zlink_config_result_t err;
int ready = zlink_poller_wait(poller, events, 16, /*timeout_ms=*/1000, &err);

for (int i = 0; i < ready; ++i) {
    if ((events[i].events & ZLINK_POLLCOMPLETION) == 0)
        continue;
    for (;;) {
        zlink_completion_t completion = {0};
        completion.struct_size = sizeof(completion);
        zlink_recv_result_t rc = zlink_completion_recv(
            events[i].socket, &completion, ZLINK_RECV_FLAGS_DONTWAIT);
        if (rc == ZLINK_RECV_NO_DATA)
            break;
        if (rc != ZLINK_RECV_OK)
            break; /* handle the typed receive failure */
        /* Match completion_id or user_context, then process the result. */
        zlink_completion_close(&completion);
    }
}

Parameters. events/event_capacity is the caller-owned output array and its size. timeout_ms and error_out follow the same convention as zlink_poll.

Return and errno. Returns the number of ready events written, 0 on timeout, or -1 with error_out/errno set on failure. Each zlink_poller_event_t reports source_kind (SOCKET/FD/TIMER — only the matching one of socket/fd/timer is valid), user_data (the borrowed pointer from registration), and events.

When to use. The returned array is caller-owned and contains no pointer into Core storage. ZLINK_POLLCOMPLETION is level-triggered: wait reports readiness but does not consume a record. For every ready socket, repeat DONTWAIT zlink_completion_recv() through ZLINK_RECV_NO_DATA, and close every successful record. One poller registration may own the completion bit for a socket; concurrent drain by another thread is unsupported. The recv_part families never drain this queue.


Readiness by source

Source POLLIN POLLOUT Additional rule
raw socket A complete record can be received A submit retry is worthwhile Per-socket receive mode applies
timer A fire count can be received Unsupported Drain with zlink_timer_recv() (Timers category)
FD Platform-readable Platform-writable Platform poll semantics

ZLINK_POLLITEMS_DFLT is a recommended initial item-count hint for stack buffers, not a readiness bit. ZLINK_HAVE_POLLER == 1 means this public poller API is present in the build.


See the Polling specification for the full rationale.