You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#934 disarms the keepalive timer in the is_ready probe, which covers a reused pooled connection. A fresh dial does not reach is_ready:
handle_call({checkout, ...}) takes the none -> start_connection branch (hackney_pool.erl:573-587).
The new conn arms idle_timeout = keepalive_timeout on entry to connected (hackney_conn.erl:845-869). keepalive_timeout is capped at 2000 ms by min/2 in hackney_pool.erl:476-482 (an infinity value also collapses to 2000 because of Erlang term order), and start_connection/6 does not take a per-checkout override (hackney_pool.erl:1005-1024).
The HTTP/1.1 branch of upgrade_to_ssl returns keep_state and leaves that state_timeout running (hackney_conn.erl:1012-1013). The HTTP/2 branch cancels it via init_h2_after_upgrade (:2995-3007).
ssl:connect/3 at :996 blocks the gen_statem. If the TLS handshake takes longer than the remaining timer, the timer fires (:915-917), the conn goes to closed, and the caller's {request, ...} hits the catch-all and gets {error, invalid_state} (:2005-2006 on 4.7.4; {error, closed} on master after Return {error, closed} for calls to a connection in closed state #936). No byte of the request has been written at that point.
Reproduced with a TCP proxy in front of api.stripe.com that delays the ClientHello: a 1900 ms delay succeeds, a 2200 ms delay fails with invalid_state, with pool enabled and protocols: [http1]. With prewarm_count: 0 the fresh-dial path is the only path, so the failure rate is simply the fraction of handshakes slower than ~2 s.
Suggested fix: arm the idle timer in connected(enter) only when owner =:= pool_pid (prewarm conns set owner => PoolPid at hackney_pool.erl:1367; fresh-dial conns set owner => Requester at :1024), cancel it in set_owner when the new owner is not the pool, and add the HTTP/2 branch's CancelIdle action to the HTTP/1.1 branch of upgrade_to_ssl.
Related: #916 (stuck in upgrade_to_ssl), #922 (handshake bounded by connect_timeout), #934, #936.
hackney 4.7.4 (also current
master).#934 disarms the keepalive timer in the
is_readyprobe, which covers a reused pooled connection. A fresh dial does not reachis_ready:handle_call({checkout, ...})takes thenone -> start_connectionbranch (hackney_pool.erl:573-587).idle_timeout = keepalive_timeouton entry toconnected(hackney_conn.erl:845-869).keepalive_timeoutis capped at 2000 ms bymin/2inhackney_pool.erl:476-482(aninfinityvalue also collapses to 2000 because of Erlang term order), andstart_connection/6does not take a per-checkout override (hackney_pool.erl:1005-1024).upgrade_to_sslreturnskeep_stateand leaves thatstate_timeoutrunning (hackney_conn.erl:1012-1013). The HTTP/2 branch cancels it viainit_h2_after_upgrade(:2995-3007).ssl:connect/3at:996blocks the gen_statem. If the TLS handshake takes longer than the remaining timer, the timer fires (:915-917), the conn goes toclosed, and the caller's{request, ...}hits the catch-all and gets{error, invalid_state}(:2005-2006on 4.7.4;{error, closed}on master after Return {error, closed} for calls to a connection in closed state #936). No byte of the request has been written at that point.Reproduced with a TCP proxy in front of
api.stripe.comthat delays the ClientHello: a 1900 ms delay succeeds, a 2200 ms delay fails withinvalid_state, withpoolenabled andprotocols: [http1]. Withprewarm_count: 0the fresh-dial path is the only path, so the failure rate is simply the fraction of handshakes slower than ~2 s.Suggested fix: arm the idle timer in
connected(enter)only whenowner =:= pool_pid(prewarm conns setowner => PoolPidathackney_pool.erl:1367; fresh-dial conns setowner => Requesterat:1024), cancel it inset_ownerwhen the new owner is not the pool, and add the HTTP/2 branch'sCancelIdleaction to the HTTP/1.1 branch ofupgrade_to_ssl.Related: #916 (stuck in
upgrade_to_ssl), #922 (handshake bounded byconnect_timeout), #934, #936.