From aed22d94a923c5a95e953459abefe3649e245c6a Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi <16166434+thalesmg@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:28:35 -0300 Subject: [PATCH 1/2] feat: return last known start error when fetching a worker's client --- src/ecpool_worker.erl | 32 +++++++++++++++++++++++++++++--- test/ecpool_SUITE.erl | 25 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/ecpool_worker.erl b/src/ecpool_worker.erl index 26708601..18254c77 100644 --- a/src/ecpool_worker.erl +++ b/src/ecpool_worker.erl @@ -60,9 +60,14 @@ }). -type start_result() :: not_started | started | {start_failed, term()} | {exit, term()} | undefined. + +-define(client_not_yet_started, client_not_yet_started). -define(set_start_result(RESULT), erlang:put(start_result, RESULT)). -define(take_start_result(), erlang:erase(start_result)). +-define(set_connect_result(RESULT), put(connect_result, RESULT)). +-define(peek_connect_result(), get(connect_result)). + %%-------------------------------------------------------------------- %% Callback %%-------------------------------------------------------------------- @@ -82,7 +87,10 @@ start_link(Pool, Id, Mod, Opts) -> %% @doc Get client/connection. --spec(client(pid()) -> {ok, Client :: pid()} | {ok, {Client :: pid(), pid()}} | {error, Reason :: term()}). +-spec(client(pid()) -> + {ok, Client :: pid()} + | {ok, {Client :: pid(), pid()}} + | {error, {disconnected, Reason :: term()}}). client(Pid) -> gen_server:call(Pid, client, infinity). @@ -178,7 +186,14 @@ handle_call(is_connected, _From, State = #state{client = Client}) when is_pid(Cl handle_call(is_connected, _From, State = #state{client = Client}) -> {reply, Client =/= undefined, State}; handle_call(client, _From, State = #state{client = undefined}) -> - {reply, {error, disconnected}, State}; + LastError = + case ?peek_connect_result() of + {error, Reason} -> + Reason; + _ -> + ?client_not_yet_started + end, + {reply, {error, {disconnected, LastError}}, State}; handle_call(client, _From, State = #state{client = Client}) -> {reply, {ok, Client}, State}; handle_call({exec, Action}, _From, State = #state{client = Client}) -> @@ -221,6 +236,7 @@ handle_cast(_Msg, State) -> handle_info({'EXIT', Pid, Reason}, State = #state{opts = Opts, supervisees = SupPids}) -> case lists:member(Pid, SupPids) of true -> + ?set_connect_result({error, Reason}), case proplists:get_value(auto_reconnect, Opts, false) of false -> {stop, {shutdown, Reason}, erase_client(Pid, State)}; Secs -> reconnect(Secs, erase_client(Pid, State)) @@ -306,7 +322,17 @@ handle_disconnect(Client, OnDisconnectList) -> lists:foreach(fun(OnDisconnectCallback) -> with_client(OnDisconnectCallback, Client) end, OnDisconnectList). -connect_internal(State) -> +connect_internal(State0) -> + case do_connect_internal(State0) of + {ok, State} -> + ?set_connect_result(ok), + {ok, State}; + {error, Reason} -> + ?set_connect_result({error, Reason}), + {error, Reason} + end. + +do_connect_internal(State) -> try connect(State) of {ok, Client} when is_pid(Client) -> erlang:link(Client), diff --git a/test/ecpool_SUITE.erl b/test/ecpool_SUITE.erl index a6fa5c02..127ccbc2 100644 --- a/test/ecpool_SUITE.erl +++ b/test/ecpool_SUITE.erl @@ -370,3 +370,28 @@ t_big_pool_dies_and_recovers(_TCConfig) -> ?assertEqual(ok, ecpool:check_pool_integrity(Pool)), ok = ecpool:stop_sup_pool(Pool), ok. + +t_client_dead_reason(_TCConfig) -> + Pool = ?FUNCTION_NAME, + Opts = [ {pool_size, 1} + , {pool_type, hash} + , {auto_reconnect, 2} + ], + {ok, _} = ecpool:start_sup_pool(Pool, test_client, Opts), + [Worker] = ecpool:workers(Pool), + {ok, Client} = ecpool_worker:client(Worker), + MRef = monitor(process, Client), + Reason = <<"this is how I died">>, + exit(Client, Reason), + receive + {'DOWN', MRef, _, _, _} -> + ok + after 1_000 -> + ct:fail("client didn't die") + end, + ?assertMatch( + {error, {disconnected, Reason}}, + ecpool_worker:client(Worker) + ), + ok = ecpool:stop_sup_pool(Pool), + ok. From be6afb007e36314ce4be957f120c1381cca9cba2 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi <16166434+thalesmg@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:07:35 -0300 Subject: [PATCH 2/2] chore: return time since registered per review request --- src/ecpool_worker.erl | 22 +++++++++++++++++----- test/ecpool_SUITE.erl | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/ecpool_worker.erl b/src/ecpool_worker.erl index 18254c77..561648d5 100644 --- a/src/ecpool_worker.erl +++ b/src/ecpool_worker.erl @@ -188,8 +188,11 @@ handle_call(is_connected, _From, State = #state{client = Client}) -> handle_call(client, _From, State = #state{client = undefined}) -> LastError = case ?peek_connect_result() of - {error, Reason} -> - Reason; + #{ result := {error, Reason} + , time := RegisteredAt + } -> + SinceMS = now_ms() - RegisteredAt, + #{reason => Reason, time_since_observed_ms => SinceMS}; _ -> ?client_not_yet_started end, @@ -236,7 +239,7 @@ handle_cast(_Msg, State) -> handle_info({'EXIT', Pid, Reason}, State = #state{opts = Opts, supervisees = SupPids}) -> case lists:member(Pid, SupPids) of true -> - ?set_connect_result({error, Reason}), + set_connect_result({error, Reason}), case proplists:get_value(auto_reconnect, Opts, false) of false -> {stop, {shutdown, Reason}, erase_client(Pid, State)}; Secs -> reconnect(Secs, erase_client(Pid, State)) @@ -325,10 +328,10 @@ handle_disconnect(Client, OnDisconnectList) -> connect_internal(State0) -> case do_connect_internal(State0) of {ok, State} -> - ?set_connect_result(ok), + set_connect_result(ok), {ok, State}; {error, Reason} -> - ?set_connect_result({error, Reason}), + set_connect_result({error, Reason}), {error, Reason} end. @@ -473,3 +476,12 @@ monitor_child(Pid) -> %% that will be handled in shutdown/2. ok end. + +set_connect_result(Result) -> + Ctx = #{ + result => Result, + time => now_ms() + }, + ?set_connect_result(Ctx). + +now_ms() -> erlang:system_time(millisecond). diff --git a/test/ecpool_SUITE.erl b/test/ecpool_SUITE.erl index 127ccbc2..0f690e31 100644 --- a/test/ecpool_SUITE.erl +++ b/test/ecpool_SUITE.erl @@ -390,7 +390,7 @@ t_client_dead_reason(_TCConfig) -> ct:fail("client didn't die") end, ?assertMatch( - {error, {disconnected, Reason}}, + {error, {disconnected, #{reason := Reason, time_since_observed_ms := _}}}, ecpool_worker:client(Worker) ), ok = ecpool:stop_sup_pool(Pool),