xtc_blocking(3)

---

xtc_blocking(3)

offload blocking work to a thread pool without stalling the loop

XTC_BLOCKING(3) Library Functions Manual XTC_BLOCKING(3)

xtc_blocking_run, xtc_blocking_submit, xtc_blocking_pool_size, xtc_blocking_shutdownoffload blocking work to a thread pool without stalling the loop

#include <xtc.h>
#include <xtc_blocking.h>

int
xtc_blocking_run(int (*fn)(void *), void *arg, int *out_result);

int
xtc_blocking_submit(int (*fn)(void *), void *arg);

int
xtc_blocking_pool_size(int nthreads);

void
xtc_blocking_shutdown(void);

A loop thread must never block in a syscall: doing so stalls every other process sharing that loop. Some work is unavoidably blocking however -- reads and fsync(2) on regular files (which are not pollable), getaddrinfo(3), and third-party libraries. The xtc_blocking facility runs such a call on a dedicated pool thread and parks the calling process until it finishes, so the loop keeps running other work meanwhile. The wakeup reuses the runtime's existing machinery: the pool thread signals completion on a pipe the calling process waits on with (), so no new scheduler integration is required.

() runs fn(arg) on a pool thread and parks the caller until it completes, storing fn's return value in *out_result. It must be called from within a process or coroutine running on a loop; called outside that context (or where the offload cannot be set up) it runs fn synchronously on the current thread -- always correct, just not yielding.

() is the fire-and-forget variant: it hands fn(arg) to the pool and returns immediately without waiting for or collecting the result. It never parks, so it is callable from any context (for example prefetch or read-ahead). The caller owns arg's lifetime until fn runs (or has fn free it); there is no completion signal.

() pins the pool to a fixed nthreads worker threads, overriding the automatic default. It must be called before the first xtc_blocking_run() or xtc_blocking_submit(); a later call returns XTC_E_INVAL (too late). By default the pool auto-sizes: it starts with a CPU-scaled worker count ((4, , online CPUs), capped at 64) and grows on demand up to 64 when work queues faster than idle workers can take it, so the offload path is neither an artificial bottleneck on a large host nor over-provisioned on a small one. An explicit size disables growth and fixes the pool at exactly that many threads.

() stops the pool, joining its threads. It is idempotent, for orderly shutdown and leak-checked test runs. A new xtc_blocking_run() after shutdown restarts the pool.

xtc_blocking_run() returns XTC_OK once fn has run. xtc_blocking_submit() returns XTC_OK if the work was enqueued, or a negative XTC_E_* code. xtc_blocking_pool_size() returns XTC_OK, or XTC_E_INVAL if called after the pool has started.

xtc_proc(3), xtc_aio(3), xtc_async(3)

The XTC Project.

July 3, 2026 Debian

View the mdoc source