Well met!
I would like to propose a draft API that allows embedders to run libzmq without spawning its internal I/O and reaper threads.
Motivation
Game engines already own a carefully scheduled main loop. These applications may want to avoid background threads, unpredictable scheduling, and explicitly decide when network progress is allowed to run within a frame. Existing APIs let applications configure the number, affinity, and priority of libzmq I/O threads, or poll application-facing socket readiness with zmq_poll / zmq_poller / ZMQ_FD. However, they do not let the host application disable libzmq-owned I/O/reaper threads while explicitly advancing internal transport progress within a host-controlled budget. For example, ZMQ_IO_THREADS=0 is only suitable for inproc://-only contexts.
The proposed model is:
- Create a context.
- Enable manual I/O mode before the first socket is created.
- Use normal libzmq socket APIs from the application loop.
- Explicitly call a
zmq_ctx_step-style function each tick/frame to advance libzmq's internal I/O and reaper work.
This aims only to make internal progress manually (publicly) advanceable instead of owned by libzmq-created threads.
Sketch of possible API
zmq_ctx_set(ctx, ZMQ_MANUAL_IO_THREADS, 1);
// Initial shape for discussion only. A real API would accept a budget.
zmq_ctx_step(ctx, timeout_ms);
zmq_ctx_step_io(ctx, io_index, timeout_ms);
zmq_ctx_step_reaper(ctx, timeout_ms);
ZMQ_MANUAL_IO_THREADS would be a draft context option and must be set before the context starts.
Example application loop
while (running) {
simulation->tick();
zmq_recv(socket, buffer, sizeof buffer, ZMQ_DONTWAIT);
zmq_send(socket, payload, payload_sz, ZMQ_DONTWAIT);
// Could run here, or in a separate networking phase, or another engine-owned worker
zmq_ctx_step(ctx, 0);
gfx->render();
}
In a single-threaded manual loop, applications would generally use non-blocking or bounded socket operations so they do not block waiting for progress that only the next zmq_ctx_step* call can provide.
Sketch of budgeting considerations
A more full-bodied implementation could look something like this:
typedef struct zmq_ctx_step_budget_t {
// Best-effort progress budget.
uint64_t max_duration_ns;
// 0 means no explicit event limit.
uint32_t max_events;
// 0 means no explicit mailbox-command limit.
uint32_t max_commands;
// 0 means no explicit timer limit.
uint32_t max_timers;
uint32_t flags;
} zmq_ctx_step_budget_t;
typedef struct zmq_ctx_step_result_t {
uint32_t events_processed;
uint32_t commands_processed;
uint32_t timers_processed;
// flags may include MORE_WORK, IDLE, BUDGET_EXHAUSTED.
uint32_t flags;
} zmq_ctx_step_result_t;
int zmq_ctx_step_budget(
void *ctx,
const zmq_ctx_step_budget_t *budget,
zmq_ctx_step_result_t *result);
Open questions
- I would like maintainer feedback on the desired public contract.
- Is such a set of APIs compatible with the direction, philosophy of libzmq?
- What transports and poller backends would need coverage before this could be accepted as draft?
- I'm sure there will be more questions if this is seriously considered for contribution back upstream 😄
Well met!
I would like to propose a draft API that allows embedders to run libzmq without spawning its internal I/O and reaper threads.
Motivation
Game engines already own a carefully scheduled main loop. These applications may want to avoid background threads, unpredictable scheduling, and explicitly decide when network progress is allowed to run within a frame. Existing APIs let applications configure the number, affinity, and priority of libzmq I/O threads, or poll application-facing socket readiness with zmq_poll / zmq_poller / ZMQ_FD. However, they do not let the host application disable libzmq-owned I/O/reaper threads while explicitly advancing internal transport progress within a host-controlled budget. For example,
ZMQ_IO_THREADS=0is only suitable for inproc://-only contexts.The proposed model is:
zmq_ctx_step-style function each tick/frame to advance libzmq's internal I/O and reaper work.This aims only to make internal progress manually (publicly) advanceable instead of owned by libzmq-created threads.
Sketch of possible API
ZMQ_MANUAL_IO_THREADSwould be a draft context option and must be set before the context starts.Example application loop
In a single-threaded manual loop, applications would generally use non-blocking or bounded socket operations so they do not block waiting for progress that only the next
zmq_ctx_step*call can provide.Sketch of budgeting considerations
A more full-bodied implementation could look something like this:
Open questions