Architecture
libjzx targets a single-process actor runtime built atop libxev.
At a high level:
- Actors communicate by sending messages.
- Messages are enqueued into actor mailboxes.
- A scheduler runs actors that have work available.
- Timers and I/O watchers feed more work into mailboxes via the libxev loop.
Runtime data flow (one loop tick)
Major components
- Loop / executor: drives libxev, timers, and ready queues. (See:
jzx_loop_run, backend wiring) - Scheduler: selects runnable actors and advances them. (See: run queue)
- Mailbox: per-actor queue with explicit backpressure behavior. (See: mailbox implementation)
- Supervision: supervisor trees that restart/replace actors on failure. (See: API model, runtime logic)
- Observability: an optional observer callback table for lifecycle and pressure signals. (See: observer callbacks, instrumentation hooks)
Code layout
include/jzx/: public C ABI headerssrc/: C runtime implementation (loop, scheduler, mailboxes, supervisors)zig/: Zig wrappers/bindings over the C ABIexamples/: runnable examples (C and Zig)tests/: tests and stress tools
Threading model
The runtime is designed to be single-process and “loop-thread owned”:
- Actor behaviors run on the loop thread (the thread calling
jzx_loop_run). - Actor mailboxes, the actor table, supervision state, and the run queue are all treated as single-threaded data structures.
There are two deliberate exceptions where other threads can participate safely:
- Timers
- The loop starts a dedicated timer thread.
- When a timer fires, the timer thread enqueues a message via the async queue (it does not directly mutate actor mailboxes).
- Cross-thread sends
jzx_send_asyncis designed to be thread-safe:- it enqueues into an internal async queue protected by a mutex
- it wakes the loop so the loop thread can deliver the message safely
Practical safety rule:
- Treat everything except
jzx_send_asyncas “call from the loop thread (or before the loop starts)”.
Invariant
The loop thread is the only thread that mutates actor mailboxes, the actor table, supervision state, and the run queue.
Other threads may only inject work indirectly (timers and jzx_send_async) and must wake the loop for delivery.
Where to read in code
Core surfaces:
- Public API + types: C ABI (
include/jzx/jzx.h) - Runtime internals: internal structs (
src/jzx_internal.h) - Scheduler/mailboxes/supervision/timers: runtime core (
src/jzx_runtime.c) - I/O watchers + wakeups: libxev integration (
src/jzx_xev.zig)