Skip to content

← Message API and ownership | Routing IDs →

Thread safety

Core uses a tiered same-handle concurrency contract.

Data path

Concurrent send operations on a supported handle are admitted. Each send, publish, or send_rid call atomically submits one part array as an independent record, so no multipart sequence state is shared across threads.

Receive is single-consumer unless a specific API states otherwise. Do not run two receive calls on the same socket concurrently. The routing-id view returned by receive is owned by the socket — do not use it after the same socket's next data-recv call (success or failure) or after close. Copy the value immediately if it must be retained.

Control path

Configuration and endpoint operations serialize internally where required. Serialization prevents data races; it does not make conflicting lifecycle changes meaningful. Configure options before traffic starts whenever possible.

Close

Close uses a stricter lifecycle gate. It returns ZLINK_CLOSE_BUSY (EBUSY) while another thread is executing an API call on the same handle. After close is accepted, new entries return ESHUTDOWN.

Pull model — no callbacks

Core registers no application callbacks. Socket data, completions, monitor events, and timer fires are all pulled by an application thread: wait for readiness with a poller, then call the relevant whole-message receive function (zlink_recv(), zlink_router_recv(), zlink_subscribe(), or zlink_xpub_recv()), zlink_completion_recv(), zlink_socket_monitor_recv(), or zlink_timer_recv(). There is therefore no "keep callbacks short" rule; the application decides which thread receives — keep one socket's receive to a single consumer.