diff --git a/src/ecpool_worker.erl b/src/ecpool_worker.erl index 26708601..561648d5 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,17 @@ 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 + #{ result := {error, Reason} + , time := RegisteredAt + } -> + SinceMS = now_ms() - RegisteredAt, + #{reason => Reason, time_since_observed_ms => SinceMS}; + _ -> + ?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 +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}), 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 +325,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), @@ -447,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 a6fa5c02..0f690e31 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 := Reason, time_since_observed_ms := _}}}, + ecpool_worker:client(Worker) + ), + ok = ecpool:stop_sup_pool(Pool), + ok.