diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl index 56c936978e55..077a34b074f1 100644 --- a/lib/sasl/src/release_handler_1.erl +++ b/lib/sasl/src/release_handler_1.erl @@ -483,27 +483,21 @@ get_opt(Tag, EvalState, Default) -> %% goes for processes that didn't respond to the suspend message. %%----------------------------------------------------------------- suspend(Mod, Procs, Timeout) -> - lists:zf(fun({_Sup, _Name, Pid, Mods}) -> - case lists:member(Mod, Mods) of - true -> - case catch sys_suspend(Pid, Timeout) of - ok -> {true, Pid}; - _ -> - % If the proc hangs, make sure to - % resume it when it gets suspended! - catch sys:resume(Pid), - false - end; - false -> - false - end - end, - Procs). - -sys_suspend(Pid, default) -> - sys:suspend(Pid); -sys_suspend(Pid, Timeout) -> - sys:suspend(Pid, Timeout). + Pids = [Pid || {_Sup, _Name, Pid, Mods} <- Procs, + lists:member(Mod, Mods)], + Results = case Timeout of + default -> sys:multi_suspend(Pids); + _ -> sys:multi_suspend(Pids, Timeout) + end, + lists:filtermap( + fun({Pid, ok}) -> + {true, Pid}; + ({Pid, _Error}) -> + % If the proc hangs, make sure to + % resume it when it gets suspended! + catch sys:resume(Pid), + false + end, Results). resume(Pids) -> lists:foreach(fun(Pid) -> catch sys:resume(Pid) end, Pids). diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl index c320fc0221eb..dd8574d0794f 100644 --- a/lib/stdlib/src/sys.erl +++ b/lib/stdlib/src/sys.erl @@ -80,6 +80,7 @@ the process itself to format these events. %% External exports -export([suspend/1, suspend/2, resume/1, resume/2, + multi_suspend/1, multi_suspend/2, get_status/1, get_status/2, get_state/1, get_state/2, replace_state/2, replace_state/3, @@ -284,6 +285,29 @@ system messages, but not other messages. Timeout :: timeout(). suspend(Name, Timeout) -> send_system_msg(Name, suspend, Timeout). +-doc(#{equiv => multi_suspend(Names, 5000)}). +-spec multi_suspend(Names) -> [{Name, Result}] when + Names :: [name()], + Name :: name(), + Result :: 'ok' | {'error', term()}. +multi_suspend(Names) -> multi_suspend(Names, 5000). + +-doc """ +Suspends multiple processes concurrently. + +Sends suspend requests to all processes in `Names` simultaneously and +collects the results. Returns a list of `{Name, Result}` tuples in +the same order as `Names`, where `Result` is `ok` on success or +`{error, Reason}` on failure. +""". +-spec multi_suspend(Names, Timeout) -> [{Name, Result}] when + Names :: [name()], + Name :: name(), + Timeout :: timeout(), + Result :: 'ok' | {'error', term()}. +multi_suspend(Names, Timeout) -> + send_multi_system_msg(Names, suspend, Timeout). + -doc(#{equiv => resume(Name, 5000)}). -spec resume(Name) -> 'ok' when Name :: name(). @@ -763,6 +787,31 @@ send_system_msg(Name, Request, Timeout) -> exit({Reason, mfa(Name, Request, Timeout)}) end. +send_multi_system_msg(Names, Request, Timeout) -> + ReqIdCol = lists:foldl( + fun(Name, Acc) -> + gen:send_request(Name, system, Request, Name, Acc) + end, gen:reqids_new(), Names), + ResultMap = collect_multi_responses(ReqIdCol, Timeout, #{}), + [{Name, maps:get(Name, ResultMap)} || Name <- Names]. + +collect_multi_responses(ReqIdCol, Timeout, Acc) -> + case gen:wait_response(ReqIdCol, Timeout, true) of + {{reply, Reply}, Name, NewReqIdCol} -> + collect_multi_responses(NewReqIdCol, Timeout, + Acc#{Name => Reply}); + {{error, {Reason, _Ref}}, Name, NewReqIdCol} -> + collect_multi_responses(NewReqIdCol, Timeout, + Acc#{Name => {error, Reason}}); + timeout -> + Remaining = gen:reqids_to_list(ReqIdCol), + lists:foldl( + fun({_ReqId, Name}, A) -> A#{Name => {error, timeout}} end, + Acc, Remaining); + no_request -> + Acc + end. + mfa(Name, {debug, {Func, Arg2}}) -> {sys, Func, [Name, Arg2]}; mfa(Name, {change_code, Mod, Vsn, Extra}) -> diff --git a/lib/stdlib/test/sys_SUITE.erl b/lib/stdlib/test/sys_SUITE.erl index e05fcde17079..7be2ab54acf1 100644 --- a/lib/stdlib/test/sys_SUITE.erl +++ b/lib/stdlib/test/sys_SUITE.erl @@ -20,9 +20,10 @@ %% %CopyrightEnd% %% -module(sys_SUITE). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2,log/1,log_to_file/1, - stats/1,trace/1,suspend/1,install/1,special_process/1]). + stats/1,trace/1,suspend/1,multi_suspend/1, + install/1,special_process/1]). -export([handle_call/3,terminate/2,init/1]). -include_lib("common_test/include/ct.hrl"). @@ -35,7 +36,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [log, log_to_file, stats, trace, suspend, install, special_process]. + [log, log_to_file, stats, trace, suspend, multi_suspend, + install, special_process]. groups() -> []. @@ -124,6 +126,29 @@ suspend(Config) when is_list(Config) -> stop(), ok. +multi_suspend(Config) when is_list(Config) -> + S1 = sys_SUITE_server1, + S2 = sys_SUITE_server2, + {ok,_} = gen_server:start_link({local,S1},?MODULE,[],[]), + {ok,_} = gen_server:start_link({local,S2},?MODULE,[],[]), + [{S1,ok},{S2,ok}] = sys:multi_suspend([S1,S2],1000), + {'EXIT',_} = (catch gen_server:call(S1,{req,48},1000)), + {'EXIT',_} = (catch gen_server:call(S2,{req,48},1000)), + {status,_,_,[_,suspended,_,_,_]} = sys:get_status(S1), + {status,_,_,[_,suspended,_,_,_]} = sys:get_status(S2), + sys:resume(S1), + sys:resume(S2), + {status,_,_,[_,running,_,_,_]} = sys:get_status(S1), + {status,_,_,[_,running,_,_,_]} = sys:get_status(S2), + [] = sys:multi_suspend([],1000), + [{S1,ok},{no_such_process,{error,noproc}}] = + sys:multi_suspend([S1,no_such_process],1000), + {status,_,_,[_,suspended,_,_,_]} = sys:get_status(S1), + sys:resume(S1), + gen_server:call(S1,stop,1000), + gen_server:call(S2,stop,1000), + ok. + install(Config) when is_list(Config) -> {ok,_Server} = start(), Master = self(),