Skip to content

Fault injection and chaos tests for the pool - #929

Open
benoitc wants to merge 4 commits into
masterfrom
test/pool-fault-injection
Open

Fault injection and chaos tests for the pool#929
benoitc wants to merge 4 commits into
masterfrom
test/pool-fault-injection

Conversation

@benoitc

@benoitc benoitc commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Based on #928, so it contains its two commits. Merge #928 first, a rebase then removes them.

Our tests only use servers which answer. Nothing covered a connection which stalls, crashes, or dies during a checkout, the case of #927.

New modules: hackney_fault_transport, a transport identical to hackney_tcp until a fault is armed on one callback; hackney_crash_sentinel, which makes a dead pool visible under error_logger:tty(false); fault tests for the checkout entry points and for the shared HTTP/2 and HTTP/3 connections; a chaos run, longer with HACKNEY_CHAOS_WORKERS and HACKNEY_CHAOS_ROUNDS; and hackney_pool_safety_tests, which reads the compiled code and fails if the pool calls a connection process outside a try.

Three problems found, corrected here:

  1. Four unprotected calls of the same kind as 4.x regression, a slow connect crashes the caller #927: three hackney_conn:set_owner/2 and the prewarm dial.
  2. stop_conn used gen_statem:stop/1, which waits without limit, so after a dial timeout the pool stayed blocked for the whole duration of the transport call. It is now bounded to 100ms, then kills the connection.
  3. h2_conn_usable/1 used get_state/1, which waits 5s, so one blocked HTTP/2 connection blocked the pool 5s per checkout on that host. is_ready, checkin_info and set_owner had the same problem. The four functions now take a timeout and the pool passes 250ms.

eunit is green (1096 tests), xref and dialyzer are clean. DEVELOPMENT.md explains how to arm a fault and how to run a longer chaos test.

aboroska and others added 4 commits August 11, 2026 09:35
Convert a connection call timeout into a checkout error. The pool
must not terminate just because a DNS/TCP/TLS attempt outlives its timeout.

Fixes: #927
Only exit:{timeout, _} was caught, so a transport raising or the conn
being killed while dialing still took the pool down. Catch any exit from
the connect call and return it as a checkout error. The caller already
stops the conn on every error return.
Integration tests only exercise servers that answer, so nothing covered a
connection that stalls, crashes, or dies mid-checkout, which is how #927
reached a release. Adds a transport that can be told to misbehave, a crash
sentinel that makes a dead pool visible under error_logger:tty(false), a
fault matrix over the checkout entry points, a randomized chaos run, and a
structural test that fails on any unguarded call from the pool into a
connection process.

The structural test found three unguarded set_owner calls and the prewarm
dial; they now go through guarded helpers. Stopping a connection from
inside the pool is bounded too: a conn wedged in a transport call used to
hold the pool for as long as the transport took to return.
Extends the fault harness to the multiplexed checkout paths, where a bad
connection is worse than on the HTTP/1 path: HTTP/2 and HTTP/3 connections
are shared, so every caller for that host goes through the same probe.

h2_conn_usable claimed a short timeout but called get_state/1, which waits
the default 5s, so one wedged connection stalled the pool for 5s per
checkout. The is_ready, checkin_info and set_owner probes had the same
5s exposure. All four now take an explicit timeout and the pool passes 250ms:
a connection that cannot answer a question about its own state at once is
unusable to the pool, and waiting on it blocks every caller behind it.

The new suite wedges a registered connection with sys:suspend/1 and fails
if the pool waits on it. Before the change that scenario hit the eunit
timeout; it now answers none in 252ms.
@benoitc

benoitc commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

Extended to the multiplexed checkout paths in 721cf78.

h2_conn_usable/1 carried a comment claiming a short timeout but called hackney_conn:get_state/1, which waits the 5s default, so a single wedged HTTP/2 connection stalled the pool for 5s on every checkout for that host. is_ready, checkin_info and set_owner had the same exposure. All four now take an explicit timeout and the pool passes 250ms.

hackney_pool_h2h3_fault_tests wedges a registered connection with sys:suspend/1 and fails if the pool waits on it. Before the change that scenario hit the eunit timeout; it now answers none in 252ms.

@benoitc
benoitc marked this pull request as ready for review August 11, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants