serversim
LearnSimulatorTemplatesPricing
…
…
Open menu
Learn↗Simulator↗Templates↗Pricing↗
My DocumentsDocs
Contents/Chapter 1414 / 16

Trap ② — a blocking call on the loop

What happens when you use an old tool that never gives the seat back? You told everyone you would call them when their turn came — and then sat at the counter waiting for your own answer.

See this first in FoundationsWhen does an event loop win, and when does it lose?→

Not every library is non-blocking: old database drivers, synchronous file IO, some HTTP clients simply stop until the result arrives. What happens when you call one on the event loop? Flip the toggle and see.

① The only thread falls asleep

There is one thread. Call a blocking function and it drops to Waiting exactly as in chapter 7, leaving the CPU. No matter how many events pile up, nobody is there to take them off the queue.

② The result: back to a one-thread server

Turn the toggle on and completion collapses. A server that was juggling dozens of requests now handles one at a time, because throughput is limited to the thread count — which is one.

③ The practical lesson

One blocking database call in a reactive stack, or one synchronous file read in Node, is enough. That is why these frameworks insist that every IO in the stack be non-blocking, and why unavoidable blocking work must be isolated onto a separate thread pool.

④ Why this mistake keeps happening

A blocking call is syntactically fine. It compiles, tests pass, and under light load it even behaves. The failure only appears once concurrency rises. Because no type system stops you, one synchronous database driver in a reactive stack, or one synchronous HTTP client inside a coroutine, slips in naturally.

⑤ Blocking ties up resources, not CPU

Draw this distinction carefully: a blocked thread uses no CPU. So utilisation looks low while the server serves nobody. Worse, if that thread is holding a database connection or a lock, the resource is held too. Many real outages are exactly this chain — a slow external call ties up a thread, the thread ties up a connection, and once the pool is dry every request to that service dies.

⑥ Hence isolation and timeouts

If blocking cannot be removed, two things are mandatory. One is isolation: confine blocking work to a dedicated pool so that exhausting it leaves the rest of the service alive. The other is timeouts: a blocking call without one holds resources for as long as the other side stays silent. That is the most common path by which an upstream outage silently becomes yours.

SpeedBlocking call

Try it in the simulator

  1. 1.See the server-side version of this trap — open the 'external dependency partial failure (cascading)' preset. The external API node is set to fail 30% of calls with +500ms latency.
  2. 2.500 RPS times a 0.56s hold (60ms WAN round trip + 500ms) needs 280 concurrent requests, but Tomcat's threads.max is only 200 — check the inspector: the HTTP client pool's max connections sits at 200/200, permanently full.
  3. 3.KPI panel: Success 50.3%, drops split across queue-full (1,285), downstream-error (1,056), and timeout (1,056) — this chapter's 'a blocked thread ties up a connection, which cascades into full failure', in numbers. Toggle off fault injection on the external node in the inspector and Success snaps back to 100%.
Try it in the simulator →

Terms in this chapter

blocking driver
A library that stops the calling thread until its result arrives.
thread isolation
Offloading blocking work to a separate pool to protect the loop.
bulkhead
Separate pools per feature so exhausting one leaves the rest alive.
pool exhaustion
Blocked threads holding every connection until the pool is empty; a full outage follows.
timeout
An upper bound on waiting. Without it, their failure is automatically yours.

One blocking line on the loop = the whole server stops.

The remaining problem

That is the methods and their traps. But why were they born in exactly this order? →

← Previous

Chapter 13. Trap ① — one CPU-bound task freezes the loop

Next →

Chapter 15. How we got here — sixty years of evolution