In a server that takes turns from a single seat, which request is the most dangerous one? Everyone agreed to use the counter briefly and step aside — then one person sits down to fill in a thirty-minute form.
See this first in FoundationsWhen does an event loop win, and when does it lose?Let us break that promise. One request does heavy CPU work — a big image transform, heavy crypto, an enormous JSON parse. Left is a thread pool, right an event loop, same traffic. Red is the heavy task.
Preemption from chapter 6. The timer interrupt reclaims the CPU from the heavy task and hands it to another thread, so one lane gets busy while everyone else keeps slipping through. The OS enforces fairness.
The loop has no such lever: once a callback starts it runs to completion, so no other event is pulled off the queue. While red owns the CPU the rest simply pile up — watch the completion counter on the right stand still.
On event-loop servers, never do heavy computation on the loop thread. Hand it to a worker thread or a separate process, or slice it and yield in between. This scene is the honest answer to "when are coroutines/Node a bad fit".
Put numbers on it. If one callback on the loop takes 10 ms, that loop serves at most 100 per second; at 50 ms, twenty. And since later requests wait for the current callback to finish, tail latency is pinned to your heaviest callback. On an event loop it is not the average but the single worst callback that sets quality.
CPU utilisation alone hides this: the core is busy at 100% while throughput refuses to rise. So runtimes expose a separate signal — event loop lag in Node, blocking-call detectors in Reactor, thread states via JFR on the JVM. Find where the time for one turn of the loop spikes and you have your culprit.
First, move the heavy work off the loop — a worker thread, a separate process, or a queue. Second, chop it up and yield: split the loop body and hand control back between chunks so other requests can slip in. Third, change the model: if per-request computation is genuinely heavy, a thread pool fits better, because OS preemption gives you fairness for free.
Try it in the simulator
Terms in this chapter
Cooperative scheduling collapses before uncooperative code.
The remaining problem
One more trap — not CPU this time, but the wrong kind of waiting →