Skip to content

Commit f44cb4c

Browse files
committed
Merge branch 'master' into release/4.4.5
2 parents 11339ea + 9d5c302 commit f44cb4c

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

‎src/hackney_pool.erl‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,11 @@ start_link(Name, Options0) ->
453453
%%====================================================================
454454

455455
init([Name, Options]) ->
456+
%% Trap exits so a supervisor shutdown (stop_pool -> terminate_child) runs
457+
%% terminate/2 instead of killing the pool outright. terminate/2 releases the
458+
%% load_regulation slots of in_use connections; without trapping exits it
459+
%% would be skipped and those per-host slots would leak.
460+
process_flag(trap_exit, true),
456461
MaxConn = case proplists:get_value(pool_size, Options) of
457462
undefined ->
458463
proplists:get_value(max_connections, Options, ?DEFAULT_MAX_CONNECTIONS);
@@ -801,7 +806,7 @@ handle_info(_Info, State) ->
801806
code_change(_OldVsn, State, _Extra) ->
802807
{ok, State}.
803808

804-
terminate(_Reason, #state{available=Available,
809+
terminate(_Reason, #state{available=Available, in_use=InUse,
805810
h2_connections=H2Conns, h3_connections=H3Conns,
806811
pid_monitors=PidMonitors}) ->
807812
%% Stop all available connections
@@ -814,6 +819,20 @@ terminate(_Reason, #state{available=Available,
814819
Available
815820
),
816821

822+
%% Release the load_regulation slot of every checked-out connection and stop
823+
%% it. Without this, stopping a pool while requests are in flight orphans the
824+
%% in_use conns: the pool's DOWN handler (which would release) is gone, so the
825+
%% global per-host slots leak and that host's concurrency cap is starved
826+
%% node-wide. Each Key carries the conn's host/port.
827+
maps:foreach(
828+
fun(Pid, Key) ->
829+
{Host, Port} = key_host_port(Key),
830+
hackney_load_regulation:release(Host, Port),
831+
stop_conn(Pid)
832+
end,
833+
InUse
834+
),
835+
817836
%% Stop all HTTP/2 connections
818837
maps:foreach(
819838
fun(_Key, Pid) ->

‎test/hackney_pool_tests.erl‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ hackney_pool_integration_test_() ->
5858
{"prewarm creates connections", fun test_prewarm/0},
5959
{"queue timeout", {timeout, 120, fun test_queue_timeout/0}},
6060
{"checkout timeout", {timeout, 120, fun test_checkout_timeout/0}},
61+
{"stop_pool releases in_use load_regulation slots",
62+
{timeout, 30, fun test_stop_pool_releases_inuse_slots/0}},
6163
{"server close detected when idle (issue #544)", {timeout, 30, fun test_server_close_detected/0}},
6264
{"bytes stranded while idle are buffered, not dropped; conn stays reusable",
6365
{timeout, 30, fun test_idle_data_consumed_not_dropped/0}},
@@ -781,6 +783,26 @@ test_checkout_timeout() ->
781783
?assertEqual(checkout_timeout, Error),
782784
hackney_pool:stop_pool(pool_test_timeout).
783785

786+
%% Regression: stopping a pool while a request is still checked out must release
787+
%% the connection's per-host load_regulation slot. Before the terminate/2 fix the
788+
%% slot leaked, starving that host's concurrency cap node-wide (and flaking
789+
%% test_checkout_timeout, which shares the host with the prior test). Delta-based
790+
%% so it does not depend on the absolute global count.
791+
test_stop_pool_releases_inuse_slots() ->
792+
Host = "127.0.0.1", Port = ?PORT,
793+
URL = <<"http://127.0.0.1:8123/pool">>,
794+
Before = hackney_load_regulation:current(Host, Port),
795+
ok = hackney_pool:start_pool(test_pool_stop_leak, [{pool_size, 1}]),
796+
Opts = [{pool, test_pool_stop_leak}, {connect_timeout, 1000},
797+
{checkout_timeout, 5000}],
798+
%% Streaming request: returns once the conn is checked out (in_use), holding
799+
%% a load_regulation slot. Do not close it - stop the pool while in flight.
800+
{ok, _Ref} = hackney:request(post, URL, [], stream, Opts),
801+
?assertEqual(Before + 1, hackney_load_regulation:current(Host, Port)),
802+
hackney_pool:stop_pool(test_pool_stop_leak),
803+
timer:sleep(200),
804+
?assertEqual(Before, hackney_load_regulation:current(Host, Port)).
805+
784806
%% Test for issue #544: Server closes idle connection, pool should detect it
785807
%% This tests that when a server closes a connection while it's idle in the pool,
786808
%% the connection process receives tcp_closed and terminates, removing itself from pool.

0 commit comments

Comments
 (0)