The reactor's hot path is single-threaded per worker; any
blocking syscall (synchronous DB query, libc getaddrinfo,
file I/O on a slow disk, CPU-heavy compute) freezes the worker
for the duration. The fix is to offload the blocking work to
another thread:
var rows = block_in_pool[List[Row]](
lambda: db.query("SELECT ..."),
cancel,
)
The handler stays in straight-line code; cancel short-
circuits if the request is cancelled mid-blocking-call.
Implementation: one AsyncRT task per call.
Each block_in_pool[T] call runs work() as a task on the
AsyncRT pool (ThreadHandle.spawn), surfaces the result back
through a heap-allocated task struct, and joins the task. A
reactor worker is itself a pool task, so its join donates: while
it waits it runs queued pool tasks, its own work() included
if no other worker has taken it. In a forked child, which has no
pool, the task is a fresh OS thread instead.
Notes on the design:
- The pool is shared with the stdlib, so there is no flare-side
pool to size or keep alive, and a task costs an enqueue rather
than a
pthread_create (~30-50 µs on Linux x86_64).
- A
work() that blocks holds its pool worker for as long as
it blocks. A reactor that donates while joining can also pick
up someone else's queued work() before it returns to its
own connections.
- The MAX_POOL_SIZE cap (32) is enforced process-wide via a
kernel-backed POSIX named semaphore shared by
block_in_pool
and resolve_async: a call that would exceed the cap raises
"pool saturated" instead of spawning the over-limit thread, so a
fan-out burst cannot thread-bomb the process. The mechanism is
fail-closed (if the semaphore cannot be opened, e.g. under fd
exhaustion, the call is refused with the same error rather than
running uncapped) and per-process (keyed by pid, since Mojo has no
module-level mutable globals).
What the split actually buys:
- Blocking syscalls inside
work() run on another pool worker
when one is free, so the kernel can schedule other reactor
workers onto the freed CPU while the syscall is waiting.
- Multi-worker parallelism: N concurrent connections on N
reactor workers each calling
block_in_pool get up to N
parallel work() executions, instead of being serialised
inside each reactor worker.
What it does NOT buy (and why):
- The submitter (handler) thread is still blocked while
work() runs. The synchronous public signature
def block_in_pool[T](work, cancel) raises -> T requires
this — to release the reactor's connection-loop while
work() is in flight we'd need either Mojo async or a
new "park this connection" reactor state. Both are
release-line follow-ups.
Cancel contract (unchanged from the in-thread fallback):
- Pre-flight: if
cancel is already flipped at entry,
raise immediately without spawning anything.
- In-flight: the user's
work() is responsible for
polling cancel if it wants to short-circuit mid-call
(the work runs on another thread that can read the cancel
cell directly). The library cannot preempt — Mojo doesn't
expose preemption and thread cancellation is unsafe under
the borrow checker.
- Post-flight: the submitter joins the task until
work() returns; on join it checks the cancel cell
one more time. If the cell flipped while work() ran,
raise with a "cancelled mid-flight (<reason>)" message
instead of returning a now-stale result.
Why join rather than poll-on-done-flag:
A previous iteration of this implementation polled a heap
done_flag byte with a 1 ms libc_usleep between checks
to surface mid-flight cancel without waiting for work()
to complete. libc_usleep hits the documented 1000-1500x
multiplier when the calling thread
participates in multi-threaded contexts (and a block_in_pool
caller is, by construction, multi-threaded). That made the 1 ms
poll behave like a 1 second poll and dragged the per-call
latency from ~50 µs to ~1.1 s. Joining sidesteps the libc-time
path entirely, so per-call latency is the spawn + work + join
cost.
The trade-off is that mid-flight cancel no longer raises
before work() returns; it raises after. Handlers that
care about fast-cancel during long work can poll cancel
inside work().