From 83e810934de4864893f3a111db7aaef4da41a3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muska=C5=82a?= Date: Wed, 15 Oct 2025 19:27:40 +0100 Subject: [PATCH] Unregister gen_server name in terminate Currently, for every registered process there's a potential race condition where when the process terminates, the supervisor can restart it before the registry processes the DOWN message. This isn't possible to avoid in all situations, but for servers that trap_exit and process `terminate` calls, this is entirely possible. This change implements this adding a call to `gen:unregister_name` just after users `terminate` callback is processed. --- lib/stdlib/src/gen_server.erl | 11 +++++--- lib/stdlib/test/gen_server_SUITE.erl | 41 ++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 6447da17156c..09921b4e0333 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -258,6 +258,7 @@ using exit signals. -record(server_data, {parent :: pid(), tag = make_ref() :: reference(), name :: term(), + server_name :: term(), module :: module(), hibernate_after :: timeout(), handle_call :: fun((Request :: term(), From :: from(), State :: term()) -> @@ -2206,7 +2207,7 @@ according to `ServerName`. enter_loop(Mod, Options, State, ServerName, Action) when is_atom(Mod), is_list(Options) -> Name = gen:get_proc_name(ServerName), - ServerData = server_data(gen:get_parent(), Name, Mod, gen:hibernate_after(Options)), + ServerData = server_data(gen:get_parent(), ServerName, Name, Mod, gen:hibernate_after(Options)), case handle_action(ServerData, Action) of error -> gen:unregister_name(Name), @@ -2231,7 +2232,7 @@ init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, Mod, Args, Options) -> Name = gen:name(Name0), - ServerData = server_data(Parent, Name, Mod, gen:hibernate_after(Options)), + ServerData = server_data(Parent, Name0, Name, Mod, gen:hibernate_after(Options)), Debug = gen:debug_options(Name, Options), case init_it(Mod, Args) of {ok, {ok, State}} -> @@ -2350,12 +2351,13 @@ cancel_timer([]) -> cancel_timer([TRef | _]) -> ok = erlang:cancel_timer(TRef, [{async, true}, {info, false}]). --compile({inline, [server_data/4, update_callback_cache/1]}). +-compile({inline, [server_data/5, update_callback_cache/1]}). -server_data(Parent, Name, Mod, HibernateAfter) -> +server_data(Parent, ServerName, Name, Mod, HibernateAfter) -> #server_data{ parent = Parent, name = Name, + server_name = ServerName, module = Mod, hibernate_after = HibernateAfter, handle_call = fun Mod:handle_call/3, @@ -2731,6 +2733,7 @@ terminate(ServerData, State, Msg, From, Class, Reason, Stacktrace, Debug) -> -spec terminate(_, _, _, _, _, _, _, _, _) -> no_return(). terminate(ServerData, State, Msg, From, Class, Reason, Stacktrace, Debug, ReportStacktrace) -> Reply = try_terminate(ServerData, State, catch_result(Class, Reason, Stacktrace)), + gen:unregister_name(ServerData#server_data.server_name), case Reply of {'EXIT', C, R, S} -> error_info(ServerData, State, Msg, From, R, S, Debug), diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index 325801e2433a..604a1eaedd62 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -57,7 +57,8 @@ undef_handle_continue/1, format_log_1/1, format_log_2/1, format_log_with_process_label/1, - reply_by_alias_with_payload/1 + reply_by_alias_with_payload/1, + terminate_unregisters/1 ]). -export([stop1/1, stop2/1, stop3/1, stop4/1, stop5/1, stop6/1, stop7/1, @@ -113,7 +114,8 @@ all() -> call_with_huge_message_queue, {group, undef_callbacks}, undef_in_terminate, undef_in_handle_info, format_log_1, format_log_2, format_log_with_process_label, - reply_by_alias_with_payload]. + reply_by_alias_with_payload, + terminate_unregisters]. groups() -> [{stop, [], @@ -3114,6 +3116,41 @@ reply_by_alias_with_payload(Config) when is_list(Config) -> ok end. +terminate_unregisters(_Config) -> + dummy_via:reset(), + + Name = {via, dummy_via, ?FUNCTION_NAME}, + Test = self(), + Parent = spawn(fun() -> + {ok, Pid} = start_link(spec_init_via, [{ok, Name}, []]), + Test ! {server, Pid}, + timer:sleep(infinity) + end), + Server = receive {server, Pid} -> Pid end, + + Session = trace:session_create(?MODULE, self(), []), + _ = trace:function(Session, {dummy_via, unregister_name, 1}, true, []), + _ = trace:process(Session, all, true, [call]), + + Ref = monitor(process, Server), + exit(Parent, shutdown), + + % ensure unregister was called + receive + {trace, Server, call, {dummy_via, unregister_name, [Name]}} -> ok + after 500 -> + ct:fail("unregister not called") + end, + receive + {'DOWN', Ref, process, Server, _} -> ok + after + 500 -> + ct:fail("server didn't terminate") + end, + trace:session_destroy(Session), + ok. + + %%-------------------------------------------------------------- %% Help functions to spec_init_* start_link(Init, Options) ->