From 2fe03e832adb11c50bcfc62679cf17779b284124 Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Wed, 27 Jul 2016 19:45:41 +0200 Subject: Reclaim literal area after purge has completed --- erts/preloaded/src/erts_code_purger.erl | 49 +++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) (limited to 'erts/preloaded/src/erts_code_purger.erl') diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl index d1e64342e0..f9208624b7 100644 --- a/erts/preloaded/src/erts_code_purger.erl +++ b/erts/preloaded/src/erts_code_purger.erl @@ -28,7 +28,36 @@ start() -> register(erts_code_purger, self()), process_flag(trap_exit, true), - loop(). + try + %% Pass bad arguments to copy_literals() in + %% order to determine purge strategy used + %% by the VM... + Res = erts_internal:copy_literals(4711, badarg), + exit({copy_literals_returned, Res}) + catch + error : badarg -> %% VM use old purge strategy + old_loop(); + error : notsup -> %% VM use new purge strategy + loop(); + Type : Reason -> + %% This should not be possible... + exit({"Unexpected copy_literals() behaviour", + {Type, Reason}}) + end. + +old_loop() -> + _ = receive + {purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> + Res = do_old_purge(Mod), + From ! {reply, purge, Res, Ref}; + + {soft_purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> + Res = do_old_soft_purge(Mod), + From ! {reply, soft_purge, Res, Ref}; + + _Other -> ignore + end, + old_loop(). loop() -> _ = receive @@ -61,7 +90,7 @@ purge(Mod) when is_atom(Mod) -> end. -do_purge(Mod) -> +do_old_purge(Mod) -> case erts_internal:copy_literals(Mod, true) of false -> {false, false}; @@ -72,6 +101,11 @@ do_purge(Mod) -> {WasPurged, DidKill} end. +do_purge(Mod) -> + DidKill = check_proc_code(erlang:processes(), Mod, true), + WasPurged = erts_internal:purge_module(Mod), + {WasPurged, DidKill}. + %% soft_purge(Module) %% Purge old code only if no procs remain that run old code. %% Return true in that case, false if procs remain (in this @@ -86,7 +120,7 @@ soft_purge(Mod) -> end. -do_soft_purge(Mod) -> +do_old_soft_purge(Mod) -> case erts_internal:copy_literals(Mod, true) of false -> true; @@ -102,6 +136,12 @@ do_soft_purge(Mod) -> end end. +do_soft_purge(Mod) -> + case check_proc_code(erlang:processes(), Mod, false) of + false -> false; + true -> erts_internal:purge_module(Mod) + end. + %% %% check_proc_code(Pids, Mod, Hard) - Send asynchronous %% requests to all processes to perform a check_process_code @@ -283,8 +323,7 @@ cpc_sched_kill(Pid, cpc_request(#cpc_static{tag = Tag, module = Mod}, Pid, AllowGc) -> erts_internal:check_process_code(Pid, Mod, [{async, {Tag, Pid, AllowGc}}, - {allow_gc, AllowGc}, - {copy_literals, true}]). + {allow_gc, AllowGc}]). cpc_request_gc(CpcS, [Pid|Pids]) -> cpc_request(CpcS, Pid, true), -- cgit v1.2.3 From 9d0638216d35ca0f21c1eea20f8daa3992ac4f71 Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Tue, 2 Aug 2016 15:58:06 +0200 Subject: Fix purge of code Ensure that we cannot get any dangling pointers into code that has been purged. This is done by a two phase purge. At first phase all fun entries pointing into the code to purge are marked for purge. All processes trying to call these funs will be suspended and by this we avoid getting new direct references into the code. When all processes has been checked, these processes are resumed. The new purge strategy now also completely ignore the existence of indirect references to the code (funs). If such exist, they will cause bad fun exceptions to the caller, but will not prevent a soft purge or cause a kill of a process having such live references during a hard purge. This since it is impossible to give any guarantees that no processes in the system have such indirect references. Even when the system is completely clean from such references, new ones can appear via distribution and/or disk. --- erts/preloaded/src/erts_code_purger.erl | 184 +++++++++++++++++++++----------- 1 file changed, 120 insertions(+), 64 deletions(-) (limited to 'erts/preloaded/src/erts_code_purger.erl') diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl index f9208624b7..ee4fcedd2d 100644 --- a/erts/preloaded/src/erts_code_purger.erl +++ b/erts/preloaded/src/erts_code_purger.erl @@ -22,42 +22,13 @@ %% Purpose : Implement system process erts_code_purger %% to handle code module purging. --export([start/0, purge/1, soft_purge/1]). +-export([start/0, purge/1, soft_purge/1, pending_purge_lambda/3]). -spec start() -> term(). start() -> register(erts_code_purger, self()), process_flag(trap_exit, true), - try - %% Pass bad arguments to copy_literals() in - %% order to determine purge strategy used - %% by the VM... - Res = erts_internal:copy_literals(4711, badarg), - exit({copy_literals_returned, Res}) - catch - error : badarg -> %% VM use old purge strategy - old_loop(); - error : notsup -> %% VM use new purge strategy - loop(); - Type : Reason -> - %% This should not be possible... - exit({"Unexpected copy_literals() behaviour", - {Type, Reason}}) - end. - -old_loop() -> - _ = receive - {purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> - Res = do_old_purge(Mod), - From ! {reply, purge, Res, Ref}; - - {soft_purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> - Res = do_old_soft_purge(Mod), - From ! {reply, soft_purge, Res, Ref}; - - _Other -> ignore - end, - old_loop(). + loop(). loop() -> _ = receive @@ -69,10 +40,43 @@ loop() -> Res = do_soft_purge(Mod), From ! {reply, soft_purge, Res, Ref}; + {test_purge, Mod, From, Type, Ref} when is_atom(Mod), is_pid(From) -> + do_test_purge(Mod, From, Type, Ref); + _Other -> ignore end, loop(). +%% +%% Processes that tries to call a fun that belongs to +%% a module that currently is being purged will end +%% up here (pending_purge_lambda) in a suspended state. +%% When the purge operation completes or aborts (soft +%% purge that failed) these processes will be resumed. +%% +pending_purge_lambda(_Module, Fun, Args) -> + %% + %% When the process is resumed, the following + %% scenarios exist: + %% * The code that the fun refers to is still + %% there due to a failed soft purge. The + %% call to the fun will succeed via apply/2. + %% * The code was purged, and a current version + %% of the module is loaded which does not + %% contain this fun. The call will result + %% in an exception being raised. + %% * The code was purged, and no current + %% version of the module is loaded. An attempt + %% to load the module (via the error_handler) + %% will be made. This may or may not succeed. + %% If the module is loaded, it may or may + %% not contain the fun. The call will + %% succeed if the error_handler was able + %% to load the module and loaded module + %% contains this fun; otherwise, an exception + %% will be raised. + %% + apply(Fun, Args). %% purge(Module) %% Kill all processes running code from *old* Module, and then purge the @@ -89,22 +93,15 @@ purge(Mod) when is_atom(Mod) -> Result end. - -do_old_purge(Mod) -> - case erts_internal:copy_literals(Mod, true) of - false -> - {false, false}; - true -> - DidKill = check_proc_code(erlang:processes(), Mod, true), - true = erts_internal:copy_literals(Mod, false), - WasPurged = erts_internal:purge_module(Mod), - {WasPurged, DidKill} - end. - do_purge(Mod) -> - DidKill = check_proc_code(erlang:processes(), Mod, true), - WasPurged = erts_internal:purge_module(Mod), - {WasPurged, DidKill}. + case erts_internal:purge_module(Mod, prepare) of + false -> + {false, false}; + true -> + DidKill = check_proc_code(erlang:processes(), Mod, true), + true = erts_internal:purge_module(Mod, complete), + {true, DidKill} + end. %% soft_purge(Module) %% Purge old code only if no procs remain that run old code. @@ -119,27 +116,17 @@ soft_purge(Mod) -> Result end. - -do_old_soft_purge(Mod) -> - case erts_internal:copy_literals(Mod, true) of +do_soft_purge(Mod) -> + case erts_internal:purge_module(Mod, prepare) of false -> true; true -> - DoPurge = check_proc_code(erlang:processes(), Mod, false), - true = erts_internal:copy_literals(Mod, false), - case DoPurge of - false -> - false; - true -> - erts_internal:purge_module(Mod), - true - end - end. - -do_soft_purge(Mod) -> - case check_proc_code(erlang:processes(), Mod, false) of - false -> false; - true -> erts_internal:purge_module(Mod) + Res = check_proc_code(erlang:processes(), Mod, false), + erts_internal:purge_module(Mod, + case Res of + false -> abort; + true -> complete + end) end. %% @@ -336,3 +323,72 @@ cpc_init(CpcS, [Pid|Pids], NoReqs) -> cpc_init(CpcS, Pids, NoReqs+1). % end of check_proc_code() implementation. + +%% +%% FOR TESTING ONLY +%% +%% do_test_purge() is for testing only. The purge is done +%% as usual, but the tester can control when to enter the +%% specific phases. +%% +do_test_purge(Mod, From, Type, Ref) when Type == true; Type == false -> + Mon = erlang:monitor(process, From), + Res = case Type of + true -> do_test_hard_purge(Mod, From, Ref, Mon); + false -> do_test_soft_purge(Mod, From, Ref, Mon) + end, + From ! {test_purge, Res, Ref}, + erlang:demonitor(Mon, [flush]), + ok; +do_test_purge(_, _, _, _) -> + ok. + +do_test_soft_purge(Mod, From, Ref, Mon) -> + PrepRes = erts_internal:purge_module(Mod, prepare), + TestRes = test_progress(started, From, Mon, Ref, ok), + case PrepRes of + false -> + _ = test_progress(continued, From, Mon, Ref, TestRes), + true; + true -> + Res = check_proc_code(erlang:processes(), Mod, false), + _ = test_progress(continued, From, Mon, Ref, TestRes), + erts_internal:purge_module(Mod, + case Res of + false -> abort; + true -> complete + end) + end. + +do_test_hard_purge(Mod, From, Ref, Mon) -> + PrepRes = erts_internal:purge_module(Mod, prepare), + TestRes = test_progress(started, From, Mon, Ref, ok), + case PrepRes of + false -> + _ = test_progress(continued, From, Mon, Ref, TestRes), + {false, false}; + true -> + DidKill = check_proc_code(erlang:processes(), Mod, true), + _ = test_progress(continued, From, Mon, Ref, TestRes), + true = erts_internal:purge_module(Mod, complete), + {true, DidKill} + end. + +test_progress(_State, _From, _Mon, _Ref, died) -> + %% Test process died; continue so we wont + %% leave the system in an inconsistent + %% state... + died; +test_progress(started, From, Mon, Ref, ok) -> + From ! {started, Ref}, + receive + {'DOWN', Mon, process, From, _} -> died; + {continue, Ref} -> ok + end; +test_progress(continued, From, Mon, Ref, ok) -> + From ! {continued, Ref}, + receive + {'DOWN', Mon, process, From, _} -> died; + {complete, Ref} -> ok + end. + -- cgit v1.2.3 From 1b4a59c405e6bd3532921d5c534e2264bb05b2eb Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Mon, 29 Aug 2016 19:21:53 +0200 Subject: Remove old purge strategy --- erts/preloaded/src/erts_code_purger.erl | 112 +++++++++----------------------- 1 file changed, 32 insertions(+), 80 deletions(-) (limited to 'erts/preloaded/src/erts_code_purger.erl') diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl index ee4fcedd2d..ad51815229 100644 --- a/erts/preloaded/src/erts_code_purger.erl +++ b/erts/preloaded/src/erts_code_purger.erl @@ -140,24 +140,13 @@ do_soft_purge(Mod) -> %% - false, and any processes refer 'Mod', false will %% returned; otherwise, true. %% -%% Requests will be sent to all processes identified by -%% Pids at once, but without allowing GC to be performed. -%% Check process code operations that are aborted due to -%% GC need, will be restarted allowing GC. However, only -%% ?MAX_CPC_GC_PROCS outstanding operation allowing GC at -%% a time will be allowed. This in order not to blow up -%% memory wise. -%% -%% We also only allow ?MAX_CPC_NO_OUTSTANDING_KILLS +%% We only allow ?MAX_CPC_NO_OUTSTANDING_KILLS %% outstanding kills. This both in order to avoid flooding %% our message queue with 'DOWN' messages and limiting the %% amount of memory used to keep references to all %% outstanding kills. %% -%% We maybe should allow more than two outstanding -%% GC requests, but for now we play it safe... --define(MAX_CPC_GC_PROCS, 2). -define(MAX_CPC_NO_OUTSTANDING_KILLS, 10). -record(cpc_static, {hard, module, tag}). @@ -172,84 +161,54 @@ check_proc_code(Pids, Mod, Hard) -> CpcS = #cpc_static{hard = Hard, module = Mod, tag = Tag}, - check_proc_code(CpcS, cpc_init(CpcS, Pids, 0), 0, [], #cpc_kill{}, true). + check_proc_code(CpcS, cpc_init(CpcS, Pids, 0), #cpc_kill{}, true). -check_proc_code(#cpc_static{hard = true}, 0, 0, [], +check_proc_code(#cpc_static{hard = true}, 0, #cpc_kill{outstanding = [], waiting = [], killed = Killed}, true) -> %% No outstanding requests. We did a hard check, so result is whether or %% not we killed any processes... Killed; -check_proc_code(#cpc_static{hard = false}, 0, 0, [], _KillState, Success) -> +check_proc_code(#cpc_static{hard = false}, 0, _KillState, Success) -> %% No outstanding requests and we did a soft check... Success; -check_proc_code(#cpc_static{hard = false, tag = Tag} = CpcS, NoReq0, NoGcReq0, - [], _KillState, false) -> +check_proc_code(#cpc_static{hard = false, tag = Tag} = CpcS, NoReq, _KillState, false) -> %% Failed soft check; just cleanup the remaining replies corresponding %% to the requests we've sent... - {NoReq1, NoGcReq1} = receive - {check_process_code, {Tag, _P, GC}, _Res} -> - case GC of - false -> {NoReq0-1, NoGcReq0}; - true -> {NoReq0, NoGcReq0-1} - end - end, - check_proc_code(CpcS, NoReq1, NoGcReq1, [], _KillState, false); -check_proc_code(#cpc_static{tag = Tag} = CpcS, NoReq0, NoGcReq0, NeedGC0, - KillState0, Success) -> - - %% Check if we should request a GC operation - {NoGcReq1, NeedGC1} = case NoGcReq0 < ?MAX_CPC_GC_PROCS of - GcOpAllowed when GcOpAllowed == false; - NeedGC0 == [] -> - {NoGcReq0, NeedGC0}; - _ -> - {NoGcReq0+1, cpc_request_gc(CpcS,NeedGC0)} - end, + receive {check_process_code, {Tag, _P}, _Res} -> ok end, + check_proc_code(CpcS, NoReq-1, _KillState, false); +check_proc_code(#cpc_static{tag = Tag} = CpcS, NoReq0, KillState0, Success) -> %% Wait for a cpc reply or 'DOWN' message - {NoReq1, NoGcReq2, Pid, Result, KillState1} = cpc_recv(Tag, - NoReq0, - NoGcReq1, - KillState0), + {NoReq1, Pid, Result, KillState1} = cpc_recv(Tag, NoReq0, KillState0), %% Check the result of the reply case Result of - aborted -> - %% Operation aborted due to the need to GC in order to - %% determine if the process is referring the module. - %% Schedule the operation for restart allowing GC... - check_proc_code(CpcS, NoReq1, NoGcReq2, [Pid|NeedGC1], KillState1, - Success); false -> %% Process not referring the module; done with this process... - check_proc_code(CpcS, NoReq1, NoGcReq2, NeedGC1, KillState1, - Success); + check_proc_code(CpcS, NoReq1, KillState1, Success); true -> %% Process referring the module... case CpcS#cpc_static.hard of false -> %% ... and soft check. The whole operation failed so %% no point continuing; clean up and fail... - check_proc_code(CpcS, NoReq1, NoGcReq2, [], KillState1, - false); + check_proc_code(CpcS, NoReq1, KillState1, false); true -> %% ... and hard check; schedule kill of it... - check_proc_code(CpcS, NoReq1, NoGcReq2, NeedGC1, - cpc_sched_kill(Pid, KillState1), Success) + check_proc_code(CpcS, NoReq1, cpc_sched_kill(Pid, KillState1), Success) end; 'DOWN' -> %% Handled 'DOWN' message - check_proc_code(CpcS, NoReq1, NoGcReq2, NeedGC1, - KillState1, Success) + check_proc_code(CpcS, NoReq1, KillState1, Success) end. -cpc_recv(Tag, NoReq, NoGcReq, #cpc_kill{outstanding = []} = KillState) -> +cpc_recv(Tag, NoReq, #cpc_kill{outstanding = []} = KillState) -> receive - {check_process_code, {Tag, Pid, GC}, Res} -> - cpc_handle_cpc(NoReq, NoGcReq, GC, Pid, Res, KillState) + {check_process_code, {Tag, Pid}, Res} -> + cpc_handle_cpc(NoReq, Pid, Res, KillState) end; -cpc_recv(Tag, NoReq, NoGcReq, +cpc_recv(Tag, NoReq, #cpc_kill{outstanding = [R0, R1, R2, R3, R4 | _]} = KillState) -> receive {'DOWN', R, process, _, _} when R == R0; @@ -257,21 +216,21 @@ cpc_recv(Tag, NoReq, NoGcReq, R == R2; R == R3; R == R4 -> - cpc_handle_down(NoReq, NoGcReq, R, KillState); - {check_process_code, {Tag, Pid, GC}, Res} -> - cpc_handle_cpc(NoReq, NoGcReq, GC, Pid, Res, KillState) + cpc_handle_down(NoReq, R, KillState); + {check_process_code, {Tag, Pid}, Res} -> + cpc_handle_cpc(NoReq, Pid, Res, KillState) end; -cpc_recv(Tag, NoReq, NoGcReq, #cpc_kill{outstanding = [R|_]} = KillState) -> +cpc_recv(Tag, NoReq, #cpc_kill{outstanding = [R|_]} = KillState) -> receive {'DOWN', R, process, _, _} -> - cpc_handle_down(NoReq, NoGcReq, R, KillState); - {check_process_code, {Tag, Pid, GC}, Res} -> - cpc_handle_cpc(NoReq, NoGcReq, GC, Pid, Res, KillState) + cpc_handle_down(NoReq, R, KillState); + {check_process_code, {Tag, Pid}, Res} -> + cpc_handle_cpc(NoReq, Pid, Res, KillState) end. -cpc_handle_down(NoReq, NoGcReq, R, #cpc_kill{outstanding = Rs, - no_outstanding = N} = KillState) -> - {NoReq, NoGcReq, undefined, 'DOWN', +cpc_handle_down(NoReq, R, #cpc_kill{outstanding = Rs, + no_outstanding = N} = KillState) -> + {NoReq, undefined, 'DOWN', cpc_sched_kill_waiting(KillState#cpc_kill{outstanding = cpc_list_rm(R, Rs), no_outstanding = N-1})}. @@ -280,10 +239,8 @@ cpc_list_rm(R, [R|Rs]) -> cpc_list_rm(R0, [R1|Rs]) -> [R1|cpc_list_rm(R0, Rs)]. -cpc_handle_cpc(NoReq, NoGcReq, false, Pid, Res, KillState) -> - {NoReq-1, NoGcReq, Pid, Res, KillState}; -cpc_handle_cpc(NoReq, NoGcReq, true, Pid, Res, KillState) -> - {NoReq, NoGcReq-1, Pid, Res, KillState}. +cpc_handle_cpc(NoReq, Pid, Res, KillState) -> + {NoReq-1, Pid, Res, KillState}. cpc_sched_kill_waiting(#cpc_kill{waiting = []} = KillState) -> KillState; @@ -308,18 +265,13 @@ cpc_sched_kill(Pid, no_outstanding = N+1, killed = true}. -cpc_request(#cpc_static{tag = Tag, module = Mod}, Pid, AllowGc) -> - erts_internal:check_process_code(Pid, Mod, [{async, {Tag, Pid, AllowGc}}, - {allow_gc, AllowGc}]). - -cpc_request_gc(CpcS, [Pid|Pids]) -> - cpc_request(CpcS, Pid, true), - Pids. +cpc_request(#cpc_static{tag = Tag, module = Mod}, Pid) -> + erts_internal:check_process_code(Pid, Mod, [{async, {Tag, Pid}}]). cpc_init(_CpcS, [], NoReqs) -> NoReqs; cpc_init(CpcS, [Pid|Pids], NoReqs) -> - cpc_request(CpcS, Pid, false), + cpc_request(CpcS, Pid), cpc_init(CpcS, Pids, NoReqs+1). % end of check_proc_code() implementation. -- cgit v1.2.3 From 7be8e2309d87dbb6e922cb0ca56f031f9e4ec12b Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Tue, 30 Aug 2016 12:16:57 +0200 Subject: Avoid selective receive in code-purger process The code purger process handles vast amounts of messages when there are lots of processes alive. A single message in the message queue that does not match will in such cases cause lots of extra work. The code purger process now always picks the first message in the message queue, and by this avoid this extra work. --- erts/preloaded/src/erts_code_purger.erl | 260 ++++++++++++++++---------------- 1 file changed, 127 insertions(+), 133 deletions(-) (limited to 'erts/preloaded/src/erts_code_purger.erl') diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl index ad51815229..a48aebe4e7 100644 --- a/erts/preloaded/src/erts_code_purger.erl +++ b/erts/preloaded/src/erts_code_purger.erl @@ -28,24 +28,29 @@ start() -> register(erts_code_purger, self()), process_flag(trap_exit, true), - loop(). - -loop() -> - _ = receive - {purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> - Res = do_purge(Mod), - From ! {reply, purge, Res, Ref}; - - {soft_purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> - Res = do_soft_purge(Mod), - From ! {reply, soft_purge, Res, Ref}; - - {test_purge, Mod, From, Type, Ref} when is_atom(Mod), is_pid(From) -> - do_test_purge(Mod, From, Type, Ref); - - _Other -> ignore - end, - loop(). + wait_for_request(). + +wait_for_request() -> + handle_request(receive Msg -> Msg end, []). + +handle_request({purge, Mod, From, Ref}, Reqs) when is_atom(Mod), is_pid(From) -> + {Res, NewReqs} = do_purge(Mod, Reqs), + From ! {reply, purge, Res, Ref}, + check_requests(NewReqs); +handle_request({soft_purge, Mod, From, Ref}, Reqs) when is_atom(Mod), is_pid(From) -> + {Res, NewReqs} = do_soft_purge(Mod, Reqs), + From ! {reply, soft_purge, Res, Ref}, + check_requests(NewReqs); +handle_request({test_purge, Mod, From, Type, Ref}, Reqs) when is_atom(Mod), is_pid(From) -> + NewReqs = do_test_purge(Mod, From, Type, Ref, Reqs), + check_requests(NewReqs); +handle_request(_Garbage, Reqs) -> + check_requests(Reqs). + +check_requests([]) -> + wait_for_request(); +check_requests([R|Rs]) -> + handle_request(R, Rs). %% %% Processes that tries to call a fun that belongs to @@ -93,14 +98,15 @@ purge(Mod) when is_atom(Mod) -> Result end. -do_purge(Mod) -> +do_purge(Mod, Reqs) -> case erts_internal:purge_module(Mod, prepare) of false -> - {false, false}; + {{false, false}, Reqs}; true -> - DidKill = check_proc_code(erlang:processes(), Mod, true), + {DidKill, NewReqs} = check_proc_code(erlang:processes(), + Mod, true, Reqs), true = erts_internal:purge_module(Mod, complete), - {true, DidKill} + {{true, DidKill}, NewReqs} end. %% soft_purge(Module) @@ -116,29 +122,26 @@ soft_purge(Mod) -> Result end. -do_soft_purge(Mod) -> +do_soft_purge(Mod, Reqs) -> case erts_internal:purge_module(Mod, prepare) of false -> - true; + {true, Reqs}; true -> - Res = check_proc_code(erlang:processes(), Mod, false), - erts_internal:purge_module(Mod, - case Res of - false -> abort; - true -> complete - end) + {PurgeOp, NewReqs} = check_proc_code(erlang:processes(), + Mod, false, Reqs), + {erts_internal:purge_module(Mod, PurgeOp), NewReqs} end. %% -%% check_proc_code(Pids, Mod, Hard) - Send asynchronous +%% check_proc_code(Pids, Mod, Hard, Preqs) - Send asynchronous %% requests to all processes to perform a check_process_code %% operation. Each process will check their own state and %% reply with the result. If 'Hard' equals %% - true, processes that refer 'Mod' will be killed. If %% any processes were killed true is returned; otherwise, %% false. -%% - false, and any processes refer 'Mod', false will -%% returned; otherwise, true. +%% - false, and any processes refer 'Mod', 'abort' will +%% be returned; otherwise, 'complete'. %% %% We only allow ?MAX_CPC_NO_OUTSTANDING_KILLS %% outstanding kills. This both in order to avoid flooding @@ -149,99 +152,91 @@ do_soft_purge(Mod) -> -define(MAX_CPC_NO_OUTSTANDING_KILLS, 10). --record(cpc_static, {hard, module, tag}). +-record(cpc_static, {hard, module, tag, purge_requests}). -record(cpc_kill, {outstanding = [], no_outstanding = 0, waiting = [], killed = false}). -check_proc_code(Pids, Mod, Hard) -> +check_proc_code(Pids, Mod, Hard, PReqs) -> Tag = erlang:make_ref(), CpcS = #cpc_static{hard = Hard, module = Mod, - tag = Tag}, - check_proc_code(CpcS, cpc_init(CpcS, Pids, 0), #cpc_kill{}, true). - -check_proc_code(#cpc_static{hard = true}, 0, - #cpc_kill{outstanding = [], waiting = [], killed = Killed}, - true) -> - %% No outstanding requests. We did a hard check, so result is whether or - %% not we killed any processes... - Killed; -check_proc_code(#cpc_static{hard = false}, 0, _KillState, Success) -> - %% No outstanding requests and we did a soft check... - Success; -check_proc_code(#cpc_static{hard = false, tag = Tag} = CpcS, NoReq, _KillState, false) -> - %% Failed soft check; just cleanup the remaining replies corresponding - %% to the requests we've sent... - receive {check_process_code, {Tag, _P}, _Res} -> ok end, - check_proc_code(CpcS, NoReq-1, _KillState, false); -check_proc_code(#cpc_static{tag = Tag} = CpcS, NoReq0, KillState0, Success) -> - - %% Wait for a cpc reply or 'DOWN' message - {NoReq1, Pid, Result, KillState1} = cpc_recv(Tag, NoReq0, KillState0), - - %% Check the result of the reply - case Result of - false -> + tag = Tag, + purge_requests = PReqs}, + cpc_receive(CpcS, cpc_init(CpcS, Pids, 0), #cpc_kill{}, []). + +cpc_receive(#cpc_static{hard = true} = CpcS, + 0, + #cpc_kill{outstanding = [], waiting = [], killed = Killed}, + PReqs) -> + %% No outstanding cpc requests. We did a hard check, so result is + %% whether or not we killed any processes... + cpc_result(CpcS, PReqs, Killed); +cpc_receive(#cpc_static{hard = false} = CpcS, 0, _KillState, PReqs) -> + %% No outstanding cpc requests and we did a soft check that succeeded... + cpc_result(CpcS, PReqs, complete); +cpc_receive(#cpc_static{tag = Tag} = CpcS, NoReq, KillState0, PReqs) -> + receive + {check_process_code, {Tag, _Pid}, false} -> %% Process not referring the module; done with this process... - check_proc_code(CpcS, NoReq1, KillState1, Success); - true -> + cpc_receive(CpcS, NoReq-1, KillState0, PReqs); + {check_process_code, {Tag, Pid}, true} -> %% Process referring the module... case CpcS#cpc_static.hard of false -> %% ... and soft check. The whole operation failed so - %% no point continuing; clean up and fail... - check_proc_code(CpcS, NoReq1, KillState1, false); + %% no point continuing; fail straight away. Garbage + %% messages from this session will be ignored + %% by following sessions... + cpc_result(CpcS, PReqs, abort); true -> %% ... and hard check; schedule kill of it... - check_proc_code(CpcS, NoReq1, cpc_sched_kill(Pid, KillState1), Success) + KillState1 = cpc_sched_kill(Pid, KillState0), + cpc_receive(CpcS, NoReq-1, KillState1, PReqs) end; - 'DOWN' -> - %% Handled 'DOWN' message - check_proc_code(CpcS, NoReq1, KillState1, Success) + {'DOWN', MonRef, process, _, _} -> + KillState1 = cpc_handle_down(MonRef, KillState0), + cpc_receive(CpcS, NoReq, KillState1, PReqs); + PReq when element(1, PReq) == purge; + element(1, PReq) == soft_purge; + element(1, PReq) == test_purge -> + %% A new purge request; save it until later... + cpc_receive(CpcS, NoReq, KillState0, [PReq | PReqs]); + _Garbage -> + %% Garbage message; ignore it... + cpc_receive(CpcS, NoReq, KillState0, PReqs) end. -cpc_recv(Tag, NoReq, #cpc_kill{outstanding = []} = KillState) -> - receive - {check_process_code, {Tag, Pid}, Res} -> - cpc_handle_cpc(NoReq, Pid, Res, KillState) - end; -cpc_recv(Tag, NoReq, - #cpc_kill{outstanding = [R0, R1, R2, R3, R4 | _]} = KillState) -> - receive - {'DOWN', R, process, _, _} when R == R0; - R == R1; - R == R2; - R == R3; - R == R4 -> - cpc_handle_down(NoReq, R, KillState); - {check_process_code, {Tag, Pid}, Res} -> - cpc_handle_cpc(NoReq, Pid, Res, KillState) - end; -cpc_recv(Tag, NoReq, #cpc_kill{outstanding = [R|_]} = KillState) -> - receive - {'DOWN', R, process, _, _} -> - cpc_handle_down(NoReq, R, KillState); - {check_process_code, {Tag, Pid}, Res} -> - cpc_handle_cpc(NoReq, Pid, Res, KillState) +cpc_result(#cpc_static{purge_requests = PReqs}, NewPReqs, Res) -> + {Res, PReqs ++ cpc_reverse(NewPReqs)}. + +cpc_reverse([_] = L) -> L; +cpc_reverse(Xs) -> cpc_reverse(Xs, []). + +cpc_reverse([], Ys) -> Ys; +cpc_reverse([X|Xs], Ys) -> cpc_reverse(Xs, [X|Ys]). + +cpc_handle_down(R, #cpc_kill{outstanding = Rs, + no_outstanding = N} = KillState0) -> + try + NewOutst = cpc_list_rm(R, Rs), + KillState1 = KillState0#cpc_kill{outstanding = NewOutst, + no_outstanding = N-1}, + cpc_sched_kill_waiting(KillState1) + catch + throw : undefined -> %% Triggered by garbage message... + KillState0 end. -cpc_handle_down(NoReq, R, #cpc_kill{outstanding = Rs, - no_outstanding = N} = KillState) -> - {NoReq, undefined, 'DOWN', - cpc_sched_kill_waiting(KillState#cpc_kill{outstanding = cpc_list_rm(R, Rs), - no_outstanding = N-1})}. - +cpc_list_rm(_R, []) -> + throw(undefined); cpc_list_rm(R, [R|Rs]) -> Rs; cpc_list_rm(R0, [R1|Rs]) -> [R1|cpc_list_rm(R0, Rs)]. -cpc_handle_cpc(NoReq, Pid, Res, KillState) -> - {NoReq-1, Pid, Res, KillState}. - cpc_sched_kill_waiting(#cpc_kill{waiting = []} = KillState) -> KillState; cpc_sched_kill_waiting(#cpc_kill{outstanding = Rs, @@ -283,64 +278,63 @@ cpc_init(CpcS, [Pid|Pids], NoReqs) -> %% as usual, but the tester can control when to enter the %% specific phases. %% -do_test_purge(Mod, From, Type, Ref) when Type == true; Type == false -> - Mon = erlang:monitor(process, From), - Res = case Type of - true -> do_test_hard_purge(Mod, From, Ref, Mon); - false -> do_test_soft_purge(Mod, From, Ref, Mon) - end, +do_test_purge(Mod, From, true, Ref, Reqs) -> + {Res, NewReqs} = do_test_hard_purge(Mod, From, Ref, Reqs), + From ! {test_purge, Res, Ref}, + NewReqs; +do_test_purge(Mod, From, false, Ref, Reqs) -> + {Res, NewReqs} = do_test_soft_purge(Mod, From, Ref, Reqs), From ! {test_purge, Res, Ref}, - erlang:demonitor(Mon, [flush]), - ok; -do_test_purge(_, _, _, _) -> - ok. + NewReqs; +do_test_purge(_, _, _, _, Reqs) -> + Reqs. -do_test_soft_purge(Mod, From, Ref, Mon) -> +do_test_soft_purge(Mod, From, Ref, Reqs) -> PrepRes = erts_internal:purge_module(Mod, prepare), - TestRes = test_progress(started, From, Mon, Ref, ok), + TestRes = test_progress(started, From, Ref, ok), case PrepRes of false -> - _ = test_progress(continued, From, Mon, Ref, TestRes), - true; + _ = test_progress(continued, From, Ref, TestRes), + {true, Reqs}; true -> - Res = check_proc_code(erlang:processes(), Mod, false), - _ = test_progress(continued, From, Mon, Ref, TestRes), - erts_internal:purge_module(Mod, - case Res of - false -> abort; - true -> complete - end) + {PurgeOp, NewReqs} = check_proc_code(erlang:processes(), + Mod, false, Reqs), + _ = test_progress(continued, From, Ref, TestRes), + {erts_internal:purge_module(Mod, PurgeOp), NewReqs} end. -do_test_hard_purge(Mod, From, Ref, Mon) -> +do_test_hard_purge(Mod, From, Ref, Reqs) -> PrepRes = erts_internal:purge_module(Mod, prepare), - TestRes = test_progress(started, From, Mon, Ref, ok), + TestRes = test_progress(started, From, Ref, ok), case PrepRes of false -> - _ = test_progress(continued, From, Mon, Ref, TestRes), - {false, false}; + _ = test_progress(continued, From, Ref, TestRes), + {{false, false}, Reqs}; true -> - DidKill = check_proc_code(erlang:processes(), Mod, true), - _ = test_progress(continued, From, Mon, Ref, TestRes), + {DidKill, NewReqs} = check_proc_code(erlang:processes(), + Mod, true, Reqs), + _ = test_progress(continued, From, Ref, TestRes), true = erts_internal:purge_module(Mod, complete), - {true, DidKill} + {{true, DidKill}, NewReqs} end. -test_progress(_State, _From, _Mon, _Ref, died) -> +test_progress(_State, _From, _Ref, died) -> %% Test process died; continue so we wont %% leave the system in an inconsistent %% state... died; -test_progress(started, From, Mon, Ref, ok) -> +test_progress(started, From, Ref, ok) -> From ! {started, Ref}, + Mon = erlang:monitor(process, From), receive {'DOWN', Mon, process, From, _} -> died; - {continue, Ref} -> ok + {continue, Ref} -> erlang:demonitor(Mon, [flush]), ok end; -test_progress(continued, From, Mon, Ref, ok) -> +test_progress(continued, From, Ref, ok) -> From ! {continued, Ref}, + Mon = erlang:monitor(process, From), receive {'DOWN', Mon, process, From, _} -> died; - {complete, Ref} -> ok + {complete, Ref} -> erlang:demonitor(Mon, [flush]), ok end. -- cgit v1.2.3 From c70ca686fe269db6079a2ca1c7e09cdfc0cfa903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 5 Sep 2016 16:16:23 +0200 Subject: Don't leak old code when loading a modules with an on_load function Normally, calling code:delete/1 before re-loading the code for a module is unnecessary but causes no problem. But there will be be problems if the new code has an on_load function. Code with an on_load function will always be loaded as old code to allowed it to be easily purged if the on_load function would fail. If the on_load function succeeds, the old and current code will be swapped. So in the scenario where code:delete/1 has been called explicitly, there is old code but no current code. Loading code with an on_load function will cause the reference to the old code to be overwritten. That will at best cause a memory leak, and at worst an emulator crash (especially if NIFs are involved). To avoid that situation, we will put the code with the on_load function in a special, third slot in Module. ERL-240 --- erts/preloaded/src/erts_code_purger.erl | 37 ++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'erts/preloaded/src/erts_code_purger.erl') diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl index ee4fcedd2d..28d71fd07e 100644 --- a/erts/preloaded/src/erts_code_purger.erl +++ b/erts/preloaded/src/erts_code_purger.erl @@ -22,7 +22,8 @@ %% Purpose : Implement system process erts_code_purger %% to handle code module purging. --export([start/0, purge/1, soft_purge/1, pending_purge_lambda/3]). +-export([start/0, purge/1, soft_purge/1, pending_purge_lambda/3, + finish_after_on_load/2]). -spec start() -> term(). start() -> @@ -40,6 +41,11 @@ loop() -> Res = do_soft_purge(Mod), From ! {reply, soft_purge, Res, Ref}; + {finish_after_on_load,{Mod,Keep},From,Ref} + when is_atom(Mod), is_pid(From) -> + Res = do_finish_after_on_load(Mod, Keep), + From ! {reply, finish_after_on_load, Res, Ref}; + {test_purge, Mod, From, Type, Ref} when is_atom(Mod), is_pid(From) -> do_test_purge(Mod, From, Type, Ref); @@ -129,6 +135,35 @@ do_soft_purge(Mod) -> end) end. +%% finish_after_on_load(Module, Keep) +%% Finish after running on_load function. If Keep is false, +%% purge the code for the on_load function. + +finish_after_on_load(Mod, Keep) -> + Ref = make_ref(), + erts_code_purger ! {finish_after_on_load, {Mod,Keep}, self(), Ref}, + receive + {reply, finish_after_on_load, Result, Ref} -> + Result + end. + +do_finish_after_on_load(Mod, Keep) -> + erlang:finish_after_on_load(Mod, Keep), + case Keep of + true -> + ok; + false -> + case erts_internal:purge_module(Mod, prepare_on_load) of + false -> + true; + true -> + _ = check_proc_code(erlang:processes(), Mod, true), + true = erts_internal:purge_module(Mod, complete) + end + end. + + + %% %% check_proc_code(Pids, Mod, Hard) - Send asynchronous %% requests to all processes to perform a check_process_code -- cgit v1.2.3