advanced24 min studyLesson 163 in Engineering

Event-loop fairness and bounded turns

Reason about starvation, runnable queues, cooperative yields, and the latency cost of long handlers.

On this page

What you’ll understand

  • Distinguish runnable delay from external I/O delay.
  • Design bounded handler turns without losing edge-triggered work.
  • Estimate a conditional scheduling-delay budget.

An event loop can remain fully occupied and still provide terrible service. One connection may always have more bytes, one callback may perform a large computation, or one runnable task may continuously enqueue itself. The engineering question is not whether the loop is busy. It is whether every admitted class of work receives enough timely attention under stated load and scheduling assumptions.

A callback runs until it gives control back

On a single event-loop thread, a handler that computes for a long uninterrupted interval prevents other handlers on that thread from executing. Asynchronous syntax does not preempt ordinary CPU instructions. A wait that is already satisfied may also return immediately, so repeating an await-shaped operation does not necessarily yield useful scheduling opportunities. Inspect the runtime’s actual scheduling semantics and measure callback execution duration separately from time spent suspended. A quick happy-path handler can hide a pathological large-input branch.

Fairness policies define which ready item runs next and how much it may do. Round-robin ready queues, byte budgets, item budgets, and elapsed-time budgets each control a different quantity. A byte limit may fail to bound expensive parsing; a time limit needs clock checks and interruptible work units. Bound each turn and preserve continuation state. Offload truly blocking operations or long CPU work through an appropriate facility, while bounding that facility’s queue instead of simply moving overload out of sight.

Dround≤(N−1)(b+h)D_{\mathrm{round}}\leq(N-1)(b+h)
For a fixed round-robin set of N runnable handlers, each other handler has a turn bounded by b seconds plus dispatch overhead h seconds. This bounds wait until the next turn only under those assumptions; new priority work, blocking calls, or unbounded handlers invalidate it.↔ Scroll wide equations horizontally.

Draining and fairness must cooperate

An edge-triggered readiness handler cannot process a small amount, forget the resource, and assume another notification will arrive for remaining data. Instead, retain ready state and put the unfinished resource back on an application-level ready queue. Later turns continue from the saved offset. Only a suitable nonblocking stopping observation allows it to return to waiting for readiness. This separates notification collection from fair execution and prevents the false choice between starving peers and losing progress.

Timers and cancellation also require dispatch time. A deadline becoming due does not imply its handler executes at that exact instant. Record event-loop lag by comparing an intended timer deadline with actual callback start using a monotonic clock. A large gap suggests scheduling or CPU contention rather than an external network delay. Instrument queue age by work class so that a high-throughput connection cannot conceal starvation of health checks, interactive traffic, or shutdown callbacks. Priority itself requires a policy against indefinite exclusion.

ℓloop=max⁡(0,tstart−tdue)\ell_{\mathrm{loop}}=\max(0,t_{\mathrm{start}}-t_{\mathrm{due}})
Loop lag is the nonnegative difference in seconds between scheduled due time and observed callback start, measured in the same monotonic clock domain. It includes dispatch contention and does not isolate a single cause.↔ Scroll wide equations horizontally.

Bound work instead of promising universal fairness

Fairness guarantees are conditional. If admitted work exceeds sustained execution capacity, every request cannot receive a fixed latency merely because the dispatcher rotates its queue. Admission control, bounded queues, and overload responses must accompany scheduling. Separate fairness across connections from fairness across tenants: a tenant opening many connections can gain many turns unless the scheduler accounts at the tenant level. Measure waiting-time distributions and rejection rates together, since rejecting most work can make the remaining latency look excellent.

In the queue lab, press Reset, set arrival rate to 4 and service rate to 3, predict growth from the initial four jobs, then press Play. Changing scheduling order alone cannot reverse this positive fluid slope. For a separate draining run, press Reset again, explicitly set arrival rate to 2 and service rate to 3, predict the slope, then press Play. Each reset restarts the model rather than preserving the preceding backlog. This model does not represent round-robin selection, timer phases, priority, or individual starvation; it demonstrates the capacity condition that any fairness policy must face. On paper, compare an unlimited draining handler with one processing a fixed byte budget and explicitly recording that the connection remains ready for another turn.

Budget a round of runnable handlers

A toy loop has exactly six continuously runnable handlers in a fixed round-robin order. Each turn takes at most 2 milliseconds of application work plus 0.1 milliseconds of dispatch overhead. No other callbacks, priorities, or blocking calls exist.

  1. A handler that just missed its turn can wait behind five other handlers. Each consumes at most 2.1 milliseconds including overhead.

  2. Multiply five by 2.1 milliseconds to get a conditional next-turn waiting bound of 10.5 milliseconds. The bound excludes the handler’s own next service time.

  3. If one handler instead blocks for 40 milliseconds, the original per-turn assumption fails. Its extra delay can propagate to every peer; the 10.5-millisecond claim no longer applies.

The result

A useful fairness budget states the bounded turn, the runnable population, and exclusions. The dispatcher name alone proves none of them.

Explore the model

Interactive lab

Interactive lab · queue

Scheduling cannot create service capacity

Press Reset, set arrival rate to 4 and service rate to 3, predict backlog growth, then press Play. Compare a fresh run after another Reset with both rates explicitly set to 2 and 3. The lab starts each run with four jobs and models rate imbalance, not timer phases, round-robin turns, or starvation.

Scheduling cannot create service capacityFluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Current readings: Fluid backlog: 4; Net rate · λ − μ: −1 /s; M/M/1 mean residence: 1 s. All parameters are adjustable with the labeled controls below.02.557.51001.534.56Elapsed time · sFluid backlogλ < μ
Fluid backlogCurrent time
Fluid backlog
4
Net rate · λ − μ
−1 /s
M/M/1 mean residence
1 s
t = 0 s

Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forFair turns and bounded admission solve different parts of responsiveness.

Check your understanding

An edge-triggered handler reaches its fairness byte budget while its socket may still contain unread data. Which design preserves progress?

Try it yourself

Four handlers rotate with a bound of 3 milliseconds of work and 0.2 milliseconds overhead per turn. Under the same fixed-population assumptions, bound next-turn waiting after a handler just misses its turn. Then explain what admitting forty more runnable handlers changes.

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Count other turns, and do not include the selected handler’s own service.

Compare with the solution

Three other turns give 3 × 3.2 = 9.6 milliseconds. With forty additional handlers, there are forty-three others and the analogous bound is 43 × 3.2 = 137.6 milliseconds. The policy still rotates fairly in this simplified sense, but its response bound worsens; admission and per-class scheduling need separate consideration.

Take it with you

  • Long uninterrupted handlers delay unrelated callbacks on the same loop.
  • A fairness budget must preserve pending readiness and continuation state.
  • Capacity limits, tenant policy, and scheduling-delay measurements complete the argument.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.