aboutsummaryrefslogtreecommitdiffstats
path: root/erts/preloaded/src/erts_code_purger.erl
diff options
context:
space:
mode:
authorRickard Green <[email protected]>2016-08-02 15:58:06 +0200
committerRickard Green <[email protected]>2016-08-29 16:26:24 +0200
commit9d0638216d35ca0f21c1eea20f8daa3992ac4f71 (patch)
treeb270d8f0226e88c376826584c4ea88ae377dff6f /erts/preloaded/src/erts_code_purger.erl
parent2fe03e832adb11c50bcfc62679cf17779b284124 (diff)
downloadotp-9d0638216d35ca0f21c1eea20f8daa3992ac4f71.tar.gz
otp-9d0638216d35ca0f21c1eea20f8daa3992ac4f71.tar.bz2
otp-9d0638216d35ca0f21c1eea20f8daa3992ac4f71.zip
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.
Diffstat (limited to 'erts/preloaded/src/erts_code_purger.erl')
-rw-r--r--erts/preloaded/src/erts_code_purger.erl184
1 files changed, 120 insertions, 64 deletions
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.
+