Bridging Sync & Async
A track of P33 · Asyncio & Coroutines.
Keep blocking work off the single-threaded loop: offload to threads or processes with to_thread/run_in_executor, and bound your queues so a fast producer cannot exhaust memory (backpressure).
Your async web service is fast until someone calls a synchronous PDF library inside a request handler. Suddenly every request hangs, not just that one — because the event loop is a single thread, and one blocking call holds it hostage. This short track is about living in the real world, where async code must talk to blocking libraries and CPU-bound work without freezing the loop.
The rule is simple and absolute: never block the loop. A slow synchronous call,
time.sleep, or heavy computation inside a coroutine stalls every other coroutine
until it returns — and await doesn't help if the work itself never yields. The
fix is to bridge out: asyncio.to_thread(fn, ...) (over run_in_executor) runs a
blocking call on a worker thread, freeing the loop; the kernel rung offloads a real
blocking function and proves the calls overlap. Pick the offload with the P32 lens:
threads for blocking I/O, a process pool for CPU-bound work, because the GIL
still applies inside those threads.
The other half is backpressure. When a producer outruns a consumer, an
unbounded queue grows until memory dies; a bounded asyncio.Queue(maxsize) makes
put wait when full, so the fast side is throttled to the rate the slow side can
drain. That closes the concurrency arc that began in P32: async for I/O-bound
fan-out, threads and processes for the blocking and CPU-bound work you bridge out
to, and bounded queues so a burst can't take the process down.