Skip to content

Commit 86180b6

Browse files
authored
Merge pull request #928 from aboroska/fix-slow-connect-crashing-pool
Fix slow connect crashing the connection pool
2 parents 6d6254b + c8c0895 commit 86180b6

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

src/hackney_pool.erl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ start_connection(Host, Port, Transport, Owner, Opts, State) ->
10141014
case hackney_conn_sup:start_conn(ConnOpts) of
10151015
{ok, Pid} ->
10161016
%% Connect the connection
1017-
case hackney_conn:connect(Pid) of
1017+
case connect_connection(Pid, ConnectTimeout) of
10181018
ok ->
10191019
%% Monitor the process
10201020
MonRef = erlang:monitor(process, Pid),
@@ -1028,6 +1028,23 @@ start_connection(Host, Port, Transport, Owner, Opts, State) ->
10281028
{error, Reason}
10291029
end.
10301030

1031+
%% @private Convert a failed connection call into a checkout error. The pool
1032+
%% must not terminate because a DNS/TCP/TLS attempt outlives its timeout, nor
1033+
%% because the connection process dies while dialing (a transport raising, or
1034+
%% the conn being killed). The caller stops the conn on any error return.
1035+
connect_connection(Pid, Timeout) ->
1036+
try hackney_conn:connect(Pid, Timeout) of
1037+
Result ->
1038+
Result
1039+
catch
1040+
exit:{timeout, _} ->
1041+
{error, connect_timeout};
1042+
exit:{Reason, {gen_statem, call, _}} ->
1043+
{error, Reason};
1044+
exit:Reason ->
1045+
{error, Reason}
1046+
end.
1047+
10311048
%% @private Process a checkin - return connection to pool.
10321049
%% Plain TCP connections are stored under their TCP key. An SSL upgraded
10331050
%% connection is stored only when it was checked out through checkout_ssl

test/hackney_pool_tests.erl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
-module(hackney_pool_tests).
1111

12+
-export([connect/4]).
13+
1214
-include_lib("eunit/include/eunit.hrl").
1315
-include("hackney.hrl").
1416

@@ -56,6 +58,10 @@ hackney_pool_integration_test_() ->
5658
{"owner crash kills connection", fun test_owner_crash/0},
5759
{"checkin resets owner to pool", fun test_checkin_resets_owner/0},
5860
{"prewarm creates connections", fun test_prewarm/0},
61+
{"connect timeout does not crash the pool",
62+
fun test_connect_timeout_does_not_crash_pool/0},
63+
{"connect crash does not crash the pool",
64+
fun test_connect_crash_does_not_crash_pool/0},
5965
{"queue timeout", {timeout, 120, fun test_queue_timeout/0}},
6066
{"checkout timeout", {timeout, 120, fun test_checkout_timeout/0}},
6167
{"stop_pool releases in_use load_regulation slots",
@@ -130,6 +136,14 @@ teardown_integration(_) ->
130136
error_logger:tty(true),
131137
ok.
132138

139+
%% Stub transport: "slow.example" outlives the connect timeout, "crash.example"
140+
%% takes the connection process down while dialing.
141+
connect("slow.example", _Port, _Opts, _Timeout) ->
142+
timer:sleep(100),
143+
{error, simulated_timeout};
144+
connect("crash.example", _Port, _Opts, _Timeout) ->
145+
erlang:error(simulated_crash).
146+
133147
setup_ssl() ->
134148
error_logger:tty(false),
135149
{ok, _} = application:ensure_all_started(cowboy),
@@ -742,6 +756,24 @@ test_prewarm() ->
742756

743757
ok = hackney_pool:stop_pool(test_pool_prewarm).
744758

759+
test_connect_timeout_does_not_crash_pool() ->
760+
PoolName = test_pool_connect_timeout,
761+
ok = hackney_pool:start_pool(PoolName, [{pool_size, 1}, {prewarm_count, 0}]),
762+
Opts = [{pool, PoolName}, {connect_timeout, 10}, {checkout_timeout, 1000}],
763+
?assertEqual({error, connect_timeout},
764+
hackney_pool:checkout("slow.example", 443, ?MODULE, Opts)),
765+
?assert(is_process_alive(hackney_pool:find_pool(PoolName))),
766+
ok = hackney_pool:stop_pool(PoolName).
767+
768+
test_connect_crash_does_not_crash_pool() ->
769+
PoolName = test_pool_connect_crash,
770+
ok = hackney_pool:start_pool(PoolName, [{pool_size, 1}, {prewarm_count, 0}]),
771+
Opts = [{pool, PoolName}, {connect_timeout, 1000}, {checkout_timeout, 2000}],
772+
?assertMatch({error, {simulated_crash, _}},
773+
hackney_pool:checkout("crash.example", 443, ?MODULE, Opts)),
774+
?assert(is_process_alive(hackney_pool:find_pool(PoolName))),
775+
ok = hackney_pool:stop_pool(PoolName).
776+
745777
%%====================================================================
746778
%% Timeout Tests
747779
%%====================================================================

0 commit comments

Comments
 (0)