Skip to content

fix: cancel the keepalive timer when a connection is checked out - #934

Open
lennartschoch wants to merge 1 commit into
benoitc:masterfrom
lennartschoch:fix/cancel-idle-timeout-on-checkout
Open

fix: cancel the keepalive timer when a connection is checked out#934
lennartschoch wants to merge 1 commit into
benoitc:masterfrom
lennartschoch:fix/cancel-idle-timeout-on-checkout

Conversation

@lennartschoch

Copy link
Copy Markdown

Ran into this after moving an Elixir app onto hackney 4. We started getting intermittent {error, invalid_state} back from Stripe calls, a handful an hour, with nothing else looking wrong.

Turns out connected(enter) arms a keepalive_timeout state_timeout on pooled conns and nothing on the checkout path resets it. is_ready/1 does a decent job on the socket itself (has_pending_close for #544, then check_socket_health) but it leaves the timer alone, so a conn can pass the probe, get handed over, and then close itself because the timer had been running since well before the probe. The request turns up after that and falls through handle_common as invalid_state.

The fix just disarms the timer in the is_ready branch that returns {ok, connected}, since a conn heading out to a requester isn't idle any more. connected(enter) re-arms it when the conn comes back to the pool, so one that gets abandoned still ages out. It's the same {state_timeout, infinity, idle_timeout} you already do on the h2 paths, which is why we only ever saw this on http1.

Probably why it hasn't come up much: you need a slow caller for the window to matter (odds are about the checkout-to-write gap over keepalive_timeout, and a pooled conn is usually near its deadline anyway), and it can't happen on h2 at all. Feels like the same family as #850, #869 and #914, just on the http1 side.

rebar3 eunit is green, 1099 tests. Added test/hackney_pool_checkout_idle_tests.erl — two of those fail without the patch, one seeing the conn already closed after a good probe and the other getting invalid_state from the request. The third is a control that the timer is still armed on release, and it passes either way.

connected(enter) arms it on a pooled conn and nothing on the checkout path
resets it, so a conn can answer {ok, connected} to is_ready/1 and still
self-close before the request lands, which then falls through handle_common
as {error, invalid_state}. Disarm it when the probe hands the conn over;
connected(enter) arms it again on release. Same thing the h2 paths already do.
@lennartschoch
lennartschoch force-pushed the fix/cancel-idle-timeout-on-checkout branch from 0dd7159 to 82d6884 Compare August 28, 2026 08:32
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.

1 participant