Bug Class 2: Missing Timeout & Runaway Retry
Slow is more dangerous than down. Absence produces an error you might handle; slowness produces nothing at all - requests queue, pools drain, and a 200ms hiccup in one service becomes a full outage of yours.
The incident
A downstream API’s p99 goes from 40ms to 900ms - not down, just slow. Your client has no deadline, so every in-flight request waits. Your retry policy has no backoff, so load triples. The thread pool saturates; now your callers see the same slowness and retry too. The postmortem calls it a “retry storm”; the root cause is two missing lines: a timeout and a backoff.
Why your tests miss it
Slowness is harder to simulate than absence - unit tests mock the call and return instantly, integration tests run at full speed. Nobody’s test suite includes a dependency that answers in 900ms.
The spec that closes it
partner_slow = fault_assumption("partner_slow",
target = partner.main,
rules = [delay(path = "/quote*", delay = "2s")],
)
fault_scenario("checkout_bounded_latency",
scenario = checkout,
faults = partner_slow,
expect = lambda r: (
assert_eq(r.status, 200, "degrade or fail fast - never hang"),
assert_true(r.duration_ms < 500,
"your latency must not track the dependency's latency"),
),
)
And the retry-amplification check - the fault fires, but the number of attempts stays bounded:
def bounded_retries(trace):
return trace.count(match.event(type = "http_request", path = "/quote*")) <= 3
always(bounded_retries)
What to assert
- End-to-end latency bounds, not just status codes -
assert_trueonr.duration_msis the assertion most suites never make. - Attempt counts under the fault: retries with backoff and a cap, not amplification.
- What the caller gets when the deadline fires: a clean timeout error, not a hang.
Which level
Protocol - the slowness must hit one operation while the service and
its healthcheck stay healthy. Syscall connect=delay() is the blunter
variant for “the whole network is slow”. Details in
Choosing Fault Levels.
Learn the pieces
- Chapter 7: HTTP Protocol Faults -
delay()rules - Chapter 15: Monitors & Temporal Properties
- The Six Bug Classes - the catalog