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) |
NAME
xtc_blocking_run,
xtc_blocking_run_off_loop,
xtc_blocking_submit,
xtc_blocking_pool_size,
xtc_blocking_shutdown —
offload blocking work to a thread pool without stalling the
loop
SYNOPSIS
#include <xtc.h>
#include <xtc_blocking.h>
int
xtc_blocking_run(int
(*fn)(void *), void
*arg, int
*out_result);
int
xtc_blocking_run_off_loop(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);
DESCRIPTION
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 pool thread wakes the
parked process through its task waker when the call finishes. No file
descriptor is used per call, so offloading keeps working when the process is
out of descriptors.
xtc_blocking_run()
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
it runs fn synchronously on the current thread --
always correct, just not yielding. On a loop it never runs
fn on the loop thread: if the offload cannot be queued
it returns an error without running fn (see
RETURN VALUES).
xtc_blocking_run_off_loop()
is the counterpart for a caller that is a plain OS thread and
not a fiber (its
xtc_self()
is none): it offloads fn(arg) to
the pool and blocks the calling thread until it completes, rather than
parking a fiber (which a bare thread cannot do), so fn
runs on a pool worker instead of inline on the caller. It remains
synchronous from the caller's point of view -- it blocks the calling thread
until fn completes -- and it does
not shorten any lock the caller holds across the call:
moving a fsync(2) off the caller thread does not shorten a
lock held around it. It is a building block for a group-commit or batching
design (submit once, wait once, wake many), not a way to let a non-fiber
caller do other work during the call: to keep serving other multiplexed
tasks during a blocking call, make those tasks fibers on a loop and use
xtc_blocking_run(), which yields. It returns
XTC_E_INVAL if fn is
NULL or if it is called from a fiber (use
xtc_blocking_run() there), and the queue errors
listed under RETURN VALUES.
xtc_blocking_submit()
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.
xtc_blocking_pool_size()
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
(max(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.
xtc_blocking_shutdown()
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.
RETURN VALUES
xtc_blocking_run() and
xtc_blocking_run_off_loop() return
XTC_OK once fn has run, with
its result in *out_result.
xtc_blocking_submit() returns
XTC_OK if the work was enqueued. All three return,
without running fn:
XTC_E_INVAL- fn is
NULL(or, forxtc_blocking_run_off_loop(), the caller is a fiber). XTC_E_AGAIN- The offload queue already holds 65536 waiting calls. The queue is bounded so that a burst the pool cannot drain becomes an error the caller sees instead of unbounded backlog; back off and retry, or shed the request.
XTC_E_RESOURCE- No pool thread could be started.
XTC_E_NOMEM- (
xtc_blocking_submit() only) the work item could not be allocated.
Through 1.55.0 each call used a pipe for its wakeup (two
descriptors per concurrent call) and, when pipe(2) failed,
ran fn inline on the loop thread, stalling every other
process on that loop. xtc_blocking_pool_size()
returns XTC_OK, or
XTC_E_INVAL if called after the pool has
started.
SEE ALSO
AUTHORS
The XTC Project.
| July 3, 2026 | Debian |