diff options
author | Björn Gustavsson <[email protected]> | 2016-05-04 15:26:38 +0200 |
---|---|---|
committer | Björn Gustavsson <[email protected]> | 2016-05-04 15:26:38 +0200 |
commit | af326dd9bc558eaf317a78f197f114d60290a536 (patch) | |
tree | ab773a37cb028cdc29712c212691300bff2586eb /lib/kernel/src | |
parent | d9f30be07f6ebb014f6021ac2bd3a99894da4c53 (diff) | |
parent | 72fc3ab8b5bd19cbd7c9d0b507d97a942d4177de (diff) | |
download | otp-af326dd9bc558eaf317a78f197f114d60290a536.tar.gz otp-af326dd9bc558eaf317a78f197f114d60290a536.tar.bz2 otp-af326dd9bc558eaf317a78f197f114d60290a536.zip |
Merge branch 'bjorn/fix-on_load/OTP-12593'
* bjorn/fix-on_load/OTP-12593:
Update documentation regarding improvements
Correctly handle multiple load attempts when on_load is pending
Avoid deadlock when an on_load function makes an external call to the module itself
code_server: Eliminate unnecessary Tag in handle_call/3
Reimplement -on_load()
Refactor erts_finish_loading() and insert_new_code()
code_SUITE: Make on_load_binary/1 clearer by using merl
Diffstat (limited to 'lib/kernel/src')
-rw-r--r-- | lib/kernel/src/code_server.erl | 243 |
1 files changed, 139 insertions, 104 deletions
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl index 90b2a06c46..6174136507 100644 --- a/lib/kernel/src/code_server.erl +++ b/lib/kernel/src/code_server.erl @@ -32,7 +32,12 @@ -import(lists, [foreach/2]). --type on_load_item() :: {reference(),module(),file:name_all(),[pid()]}. +-type on_load_action() :: + fun((term(), state()) -> {'reply',term(),state()} | + {'noreply',state()}). + +-type on_load_item() :: {{pid(),reference()},module(), + [{pid(),on_load_action()}]}. -record(state, {supervisor :: pid(), root :: file:name_all(), @@ -142,7 +147,7 @@ reply(Pid, Res) -> loop(#state{supervisor=Supervisor}=State0) -> receive {code_call, Pid, Req} -> - case handle_call(Req, {Pid, call}, State0) of + case handle_call(Req, Pid, State0) of {reply, Res, State} -> _ = reply(Pid, Res), loop(State); @@ -155,8 +160,8 @@ loop(#state{supervisor=Supervisor}=State0) -> system_terminate(Reason, Supervisor, [], State0); {system, From, Msg} -> handle_system_msg(running,Msg, From, Supervisor, State0); - {'DOWN',Ref,process,_,Res} -> - State = finish_on_load(Ref, Res, State0), + {'DOWN',Ref,process,Pid,Res} -> + State = finish_on_load({Pid,Ref}, Res, State0), loop(State); _Msg -> loop(State0) @@ -225,90 +230,90 @@ system_code_change(State, _Module, _OldVsn, _Extra) -> %% The gen_server call back functions. %% -handle_call({stick_dir,Dir}, {_From,_Tag}, S) -> +handle_call({stick_dir,Dir}, _From, S) -> {reply,stick_dir(Dir, true, S),S}; -handle_call({unstick_dir,Dir}, {_From,_Tag}, S) -> +handle_call({unstick_dir,Dir}, _From, S) -> {reply,stick_dir(Dir, false, S),S}; -handle_call({stick_mod,Mod}, {_From,_Tag}, S) -> +handle_call({stick_mod,Mod}, _From, S) -> {reply,stick_mod(Mod, true, S),S}; -handle_call({unstick_mod,Mod}, {_From,_Tag}, S) -> +handle_call({unstick_mod,Mod}, _From, S) -> {reply,stick_mod(Mod, false, S),S}; -handle_call({dir,Dir}, {_From,_Tag}, S) -> +handle_call({dir,Dir}, _From, S) -> Root = S#state.root, Resp = do_dir(Root,Dir,S#state.namedb), {reply,Resp,S}; -handle_call({load_file,Mod}, Caller, St) when is_atom(Mod) -> - load_file(Mod, Caller, St); +handle_call({load_file,Mod}, From, St) when is_atom(Mod) -> + load_file(Mod, From, St); -handle_call({add_path,Where,Dir0}, {_From,_Tag}, +handle_call({add_path,Where,Dir0}, _From, #state{namedb=Namedb,path=Path0}=S) -> {Resp,Path} = add_path(Where, Dir0, Path0, Namedb), {reply,Resp,S#state{path=Path}}; -handle_call({add_paths,Where,Dirs0}, {_From,_Tag}, +handle_call({add_paths,Where,Dirs0}, _From, #state{namedb=Namedb,path=Path0}=S) -> {Resp,Path} = add_paths(Where, Dirs0, Path0, Namedb), {reply,Resp,S#state{path=Path}}; -handle_call({set_path,PathList}, {_From,_Tag}, +handle_call({set_path,PathList}, _From, #state{path=Path0,namedb=Namedb}=S) -> {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb), {reply,Resp,S#state{path=Path,namedb=NewDb}}; -handle_call({del_path,Name}, {_From,_Tag}, +handle_call({del_path,Name}, _From, #state{path=Path0,namedb=Namedb}=S) -> {Resp,Path} = del_path(Name, Path0, Namedb), {reply,Resp,S#state{path=Path}}; -handle_call({replace_path,Name,Dir}, {_From,_Tag}, +handle_call({replace_path,Name,Dir}, _From, #state{path=Path0,namedb=Namedb}=S) -> {Resp,Path} = replace_path(Name, Dir, Path0, Namedb), {reply,Resp,S#state{path=Path}}; -handle_call(get_path, {_From,_Tag}, S) -> +handle_call(get_path, _From, S) -> {reply,S#state.path,S}; %% Messages to load, delete and purge modules/files. -handle_call({load_abs,File,Mod}, Caller, S) when is_atom(Mod) -> +handle_call({load_abs,File,Mod}, From, S) when is_atom(Mod) -> case modp(File) of false -> {reply,{error,badarg},S}; true -> - load_abs(File, Mod, Caller, S) + load_abs(File, Mod, From, S) end; -handle_call({load_binary,Mod,File,Bin}, Caller, S) when is_atom(Mod) -> - do_load_binary(Mod, File, Bin, Caller, S); +handle_call({load_binary,Mod,File,Bin}, From, S) when is_atom(Mod) -> + do_load_binary(Mod, File, Bin, From, S); -handle_call({load_native_partial,Mod,Bin}, {_From,_Tag}, S) -> +handle_call({load_native_partial,Mod,Bin}, _From, S) -> Architecture = erlang:system_info(hipe_architecture), Result = (catch hipe_unified_loader:load(Mod, Bin, Architecture)), Status = hipe_result_to_status(Result, S), {reply,Status,S}; -handle_call({load_native_sticky,Mod,Bin,WholeModule}, {_From,_Tag}, S) -> +handle_call({load_native_sticky,Mod,Bin,WholeModule}, _From, S) -> Architecture = erlang:system_info(hipe_architecture), Result = (catch hipe_unified_loader:load_module(Mod, Bin, WholeModule, Architecture)), Status = hipe_result_to_status(Result, S), {reply,Status,S}; -handle_call({ensure_loaded,Mod}, Caller, St) when is_atom(Mod) -> +handle_call({ensure_loaded,Mod}, From, St) when is_atom(Mod) -> case erlang:module_loaded(Mod) of true -> {reply,{module,Mod},St}; false when St#state.mode =:= interactive -> - load_file(Mod, Caller, St); + ensure_loaded(Mod, From, St); false -> {reply,{error,embedded},St} end; -handle_call({delete,Mod}, {_From,_Tag}, St) when is_atom(Mod) -> +handle_call({delete,Mod}, _From, St) when is_atom(Mod) -> case catch erlang:delete_module(Mod) of true -> ets:delete(St#state.moddb, Mod), @@ -317,48 +322,50 @@ handle_call({delete,Mod}, {_From,_Tag}, St) when is_atom(Mod) -> {reply,false,St} end; -handle_call({purge,Mod}, {_From,_Tag}, St) when is_atom(Mod) -> +handle_call({purge,Mod}, _From, St) when is_atom(Mod) -> {reply,do_purge(Mod),St}; -handle_call({soft_purge,Mod}, {_From,_Tag}, St) when is_atom(Mod) -> +handle_call({soft_purge,Mod}, _From, St) when is_atom(Mod) -> {reply,do_soft_purge(Mod),St}; -handle_call({is_loaded,Mod}, {_From,_Tag}, St) when is_atom(Mod) -> +handle_call({is_loaded,Mod}, _From, St) when is_atom(Mod) -> {reply,is_loaded(Mod, St#state.moddb),St}; -handle_call(all_loaded, {_From,_Tag}, S) -> +handle_call(all_loaded, _From, S) -> Db = S#state.moddb, {reply,all_loaded(Db),S}; -handle_call({get_object_code,Mod}, {_From,_Tag}, St) when is_atom(Mod) -> +handle_call({get_object_code,Mod}, _From, St) when is_atom(Mod) -> Path = St#state.path, case mod_to_bin(Path, Mod) of {_,Bin,FName} -> {reply,{Mod,Bin,FName},St}; Error -> {reply,Error,St} end; -handle_call({is_sticky, Mod}, {_From,_Tag}, S) -> +handle_call({is_sticky, Mod}, _From, S) -> Db = S#state.moddb, {reply, is_sticky(Mod,Db), S}; -handle_call(stop,{_From,_Tag}, S) -> +handle_call(stop,_From, S) -> {stop,normal,stopped,S}; -handle_call({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}, {_From,_Tag}, S=#state{mode=Mode}) -> - case erl_prim_loader:set_primary_archive(File, ArchiveBin, FileInfo, ParserFun) of +handle_call({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}, + _From, S=#state{mode=Mode}) -> + case erl_prim_loader:set_primary_archive(File, ArchiveBin, FileInfo, + ParserFun) of {ok, Files} -> {reply, {ok, Mode, Files}, S}; {error, _Reason} = Error -> {reply, Error, S} end; -handle_call(get_mode, {_From,_Tag}, S=#state{mode=Mode}) -> +handle_call(get_mode, _From, S=#state{mode=Mode}) -> {reply, Mode, S}; -handle_call({finish_loading,Prepared,EnsureLoaded}, {_,_}, S) -> +handle_call({finish_loading,Prepared,EnsureLoaded}, _From, S) -> {reply,finish_loading(Prepared, EnsureLoaded, S),S}; -handle_call(Other,{_From,_Tag}, S) -> +handle_call(Other,_From, S) -> error_msg(" ** Codeserver*** ignoring ~w~n ",[Other]), {noreply,S}. @@ -1054,14 +1061,14 @@ add_paths(Where,[Dir|Tail],Path,NameDb) -> add_paths(_,_,Path,_) -> {ok,Path}. -do_load_binary(Module, File, Binary, Caller, St) -> +do_load_binary(Module, File, Binary, From, St) -> case modp(File) andalso is_binary(Binary) of true -> case erlang:module_loaded(Module) of true -> do_purge(Module); false -> ok end, - try_load_module(File, Module, Binary, Caller, St); + try_load_module(File, Module, Binary, From, St); false -> {reply,{error,badarg},St} end. @@ -1070,63 +1077,61 @@ modp(Atom) when is_atom(Atom) -> true; modp(List) when is_list(List) -> int_list(List); modp(_) -> false. -load_abs(File, Mod, Caller, St) -> +load_abs(File, Mod, From, St) -> Ext = objfile_extension(), FileName0 = lists:concat([File, Ext]), FileName = absname(FileName0), case erl_prim_loader:get_file(FileName) of {ok,Bin,_} -> - try_load_module(FileName, Mod, Bin, Caller, St); + try_load_module(FileName, Mod, Bin, From, St); error -> {reply,{error,nofile},St} end. -try_load_module(File, Mod, Bin, {From,_}=Caller, St0) -> - case pending_on_load(Mod, From, St0) of - no -> - try_load_module_1(File, Mod, Bin, Caller, St0); - {yes,St} -> - {noreply,St} - end. +try_load_module(File, Mod, Bin, From, St) -> + Action = fun(_, S) -> + try_load_module_1(File, Mod, Bin, From, S) + end, + handle_pending_on_load(Action, Mod, From, St). -try_load_module_1(File, Mod, Bin, Caller, #state{moddb=Db}=St) -> +try_load_module_1(File, Mod, Bin, From, #state{moddb=Db}=St) -> case is_sticky(Mod, Db) of true -> %% Sticky file reject the load error_msg("Can't load module '~w' that resides in sticky dir\n",[Mod]), {reply,{error,sticky_directory},St}; false -> Architecture = erlang:system_info(hipe_architecture), - try_load_module_2(File, Mod, Bin, Caller, Architecture, St) + try_load_module_2(File, Mod, Bin, From, Architecture, St) end. -try_load_module_2(File, Mod, Bin, Caller, undefined, St) -> - try_load_module_3(File, Mod, Bin, Caller, undefined, St); -try_load_module_2(File, Mod, Bin, Caller, Architecture, +try_load_module_2(File, Mod, Bin, From, undefined, St) -> + try_load_module_3(File, Mod, Bin, From, undefined, St); +try_load_module_2(File, Mod, Bin, From, Architecture, #state{moddb=Db}=St) -> case catch hipe_unified_loader:load_native_code(Mod, Bin, Architecture) of {module,Mod} = Module -> ets:insert(Db, [{{native,Mod},true},{Mod,File}]), {reply,Module,St}; no_native -> - try_load_module_3(File, Mod, Bin, Caller, Architecture, St); + try_load_module_3(File, Mod, Bin, From, Architecture, St); Error -> error_msg("Native loading of ~ts failed: ~p\n", [File,Error]), {reply,ok,St} end. -try_load_module_3(File, Mod, Bin, Caller, Architecture, - #state{moddb=Db}=St) -> - case erlang:load_module(Mod, Bin) of - {module,Mod} = Module -> - ets:insert(Db, {Mod,File}), - post_beam_load([Mod], Architecture, St), - {reply,Module,St}; - {error,on_load} -> - handle_on_load(Mod, File, Caller, St); - {error,What} = Error -> - error_msg("Loading of ~ts failed: ~p\n", [File, What]), - {reply,Error,St} - end. +try_load_module_3(File, Mod, Bin, From, Architecture, St0) -> + Action = fun({module,_}=Module, #state{moddb=Db}=S) -> + ets:insert(Db, {Mod,File}), + post_beam_load([Mod], Architecture, S), + {reply,Module,S}; + ({error,on_load_failure}=Error, S) -> + {reply,Error,S}; + ({error,What}=Error, S) -> + error_msg("Loading of ~ts failed: ~p\n", [File, What]), + {reply,Error,S} + end, + Res = erlang:load_module(Mod, Bin), + handle_on_load(Res, Action, Mod, From, St0). hipe_result_to_status(Result, #state{moddb=Db}) -> case Result of @@ -1151,18 +1156,29 @@ int_list([H|T]) when is_integer(H) -> int_list(T); int_list([_|_]) -> false; int_list([]) -> true. -load_file(Mod, {From,_}=Caller, St0) -> - case pending_on_load(Mod, From, St0) of - no -> load_file_1(Mod, Caller, St0); - {yes,St} -> {noreply,St} - end. - -load_file_1(Mod, Caller, #state{path=Path}=St) -> +ensure_loaded(Mod, From, St0) -> + Action = fun(_, S) -> + case erlang:module_loaded(Mod) of + true -> + {reply,{module,Mod},S}; + false -> + load_file_1(Mod, From, S) + end + end, + handle_pending_on_load(Action, Mod, From, St0). + +load_file(Mod, From, St0) -> + Action = fun(_, S) -> + load_file_1(Mod, From, S) + end, + handle_pending_on_load(Action, Mod, From, St0). + +load_file_1(Mod, From, #state{path=Path}=St) -> case mod_to_bin(Path, Mod) of error -> {reply,{error,nofile},St}; {Mod,Binary,File} -> - try_load_module_1(File, Mod, Binary, Caller, St) + try_load_module_1(File, Mod, Binary, From, St) end. mod_to_bin([Dir|Tail], Mod) -> @@ -1305,59 +1321,78 @@ run([F|Fs], Data0) -> %% The on_load functionality. %% ------------------------------------------------------- -handle_on_load(Mod, File, {From,_}, #state{on_load=OnLoad0}=St0) -> +handle_on_load({error,on_load}, Action, Mod, From, St0) -> + #state{on_load=OnLoad0} = St0, Fun = fun() -> Res = erlang:call_on_load_function(Mod), exit(Res) end, - {_,Ref} = spawn_monitor(Fun), - OnLoad = [{Ref,Mod,File,[From]}|OnLoad0], + PidRef = spawn_monitor(Fun), + PidAction = {From,Action}, + OnLoad = [{PidRef,Mod,[PidAction]}|OnLoad0], St = St0#state{on_load=OnLoad}, - {noreply,St}. + {noreply,St}; +handle_on_load(Res, Action, _, _, St) -> + Action(Res, St). -pending_on_load(_, _, #state{on_load=[]}) -> - no; -pending_on_load(Mod, From, #state{on_load=OnLoad0}=St) -> - case lists:keymember(Mod, 2, OnLoad0) of +handle_pending_on_load(Action, Mod, From, #state{on_load=OnLoad0}=St) -> + case lists:keyfind(Mod, 2, OnLoad0) of false -> - no; - true -> - OnLoad = pending_on_load_1(Mod, From, OnLoad0), - {yes,St#state{on_load=OnLoad}} + Action(ok, St); + {{From,_Ref},Mod,_Pids} -> + %% The on_load function tried to make an external + %% call to its own module. That would be a deadlock. + %% Fail the call. (The call is probably from error_handler, + %% and it will ignore the actual error reason and cause + %% an undef execption.) + {reply,{error,deadlock},St}; + {_,_,_} -> + OnLoad = handle_pending_on_load_1(Mod, {From,Action}, OnLoad0), + {noreply,St#state{on_load=OnLoad}} end. -pending_on_load_1(Mod, From, [{Ref,Mod,File,Pids}|T]) -> - [{Ref,Mod,File,[From|Pids]}|T]; -pending_on_load_1(Mod, From, [H|T]) -> - [H|pending_on_load_1(Mod, From, T)]; -pending_on_load_1(_, _, []) -> []. +handle_pending_on_load_1(Mod, From, [{PidRef,Mod,Pids}|T]) -> + [{PidRef,Mod,[From|Pids]}|T]; +handle_pending_on_load_1(Mod, From, [H|T]) -> + [H|handle_pending_on_load_1(Mod, From, T)]; +handle_pending_on_load_1(_, _, []) -> []. -finish_on_load(Ref, OnLoadRes, #state{on_load=OnLoad0,moddb=Db}=State) -> - case lists:keyfind(Ref, 1, OnLoad0) of +finish_on_load(PidRef, OnLoadRes, #state{on_load=OnLoad0}=St0) -> + case lists:keyfind(PidRef, 1, OnLoad0) of false -> %% Since this process in general silently ignores messages %% it doesn't understand, it should also ignore a 'DOWN' %% message with an unknown reference. - State; - {Ref,Mod,File,WaitingPids} -> - finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db), - OnLoad = [E || {R,_,_,_}=E <- OnLoad0, R =/= Ref], - State#state{on_load=OnLoad} + St0; + {PidRef,Mod,Waiting} -> + St = finish_on_load_1(Mod, OnLoadRes, Waiting, St0), + OnLoad = [E || {R,_,_}=E <- OnLoad0, R =/= PidRef], + St#state{on_load=OnLoad} end. -finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db) -> +finish_on_load_1(Mod, OnLoadRes, Waiting, St) -> Keep = OnLoadRes =:= ok, erlang:finish_after_on_load(Mod, Keep), Res = case Keep of false -> _ = finish_on_load_report(Mod, OnLoadRes), + _ = erts_code_purger:purge(Mod), {error,on_load_failure}; true -> - ets:insert(Db, {Mod,File}), {module,Mod} end, - _ = [reply(Pid, Res) || Pid <- WaitingPids], - ok. + finish_on_load_2(Waiting, Res, St). + +finish_on_load_2([{Pid,Action}|T], Res, St0) -> + case Action(Res, St0) of + {reply,Rep,St} -> + _ = reply(Pid, Rep), + finish_on_load_2(T, Res, St); + {noreply,St} -> + finish_on_load_2(T, Res, St) + end; +finish_on_load_2([], _, St) -> + St. finish_on_load_report(_Mod, Atom) when is_atom(Atom) -> %% No error reports for atoms. |