aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel')
-rw-r--r--lib/kernel/doc/src/code.xml136
-rw-r--r--lib/kernel/doc/src/heart.xml63
-rw-r--r--lib/kernel/src/code.erl317
-rw-r--r--lib/kernel/src/code_server.erl111
-rw-r--r--lib/kernel/src/erl_epmd.erl18
-rw-r--r--lib/kernel/src/gen_tcp.erl3
-rw-r--r--lib/kernel/src/heart.erl182
-rw-r--r--lib/kernel/src/hipe_unified_loader.erl12
-rw-r--r--lib/kernel/src/kernel.appup.src4
-rw-r--r--lib/kernel/test/Makefile3
-rw-r--r--lib/kernel/test/code_SUITE.erl4
-rw-r--r--lib/kernel/test/erl_prim_loader_SUITE.erl37
-rw-r--r--lib/kernel/test/heart_SUITE.erl67
-rw-r--r--lib/kernel/test/multi_load_SUITE.erl412
14 files changed, 1282 insertions, 87 deletions
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index d4c9a48901..819da544c3 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -310,6 +310,10 @@
<datatype>
<name name="load_error_rsn"/>
</datatype>
+ <datatype>
+ <name name="prepared_code"/>
+ <desc>An opaque term holding prepared code.</desc>
+ </datatype>
</datatypes>
<funcs>
@@ -479,6 +483,138 @@
</desc>
</func>
<func>
+ <name name="atomic_load" arity="1"/>
+ <fsummary>Load a list of modules atomically</fsummary>
+ <desc>
+ <p>Tries to load all of the modules in the list
+ <c><anno>Modules</anno></c> atomically. That means that
+ either all modules are loaded at the same time, or
+ none of the modules are loaded if there is a problem with any
+ of the modules.</p>
+ <p>Loading can fail for one the following reasons:</p>
+ <taglist>
+ <tag><c>badfile</c></tag>
+ <item>
+ <p>The object code has an incorrect format or the module
+ name in the object code is not the expected module name.</p>
+ </item>
+ <tag><c>nofile</c></tag>
+ <item>
+ <p>No file with object code exists.</p>
+ </item>
+ <tag><c>on_load_not_allowed</c></tag>
+ <item>
+ <p>A module contains an
+ <seealso marker="doc/reference_manual:code_loading#on_load">-on_load function</seealso>.</p>
+ </item>
+ <tag><c>duplicated</c></tag>
+ <item>
+ <p>A module is included more than once in
+ <c><anno>Modules</anno></c>.</p>
+ </item>
+ <tag><c>not_purged</c></tag>
+ <item>
+ <p>The object code can not be loaded because an old version
+ of the code already exists.</p>
+ </item>
+ <tag><c>sticky_directory</c></tag>
+ <item>
+ <p>The object code resides in a sticky directory.</p>
+ </item>
+ <tag><c>pending_on_load</c></tag>
+ <item>
+ <p>A previously loaded module contains an
+ <c>-on_load</c> function that never finished.</p>
+ </item>
+ </taglist>
+ <p>If it is important to minimize the time that an application
+ is inactive while changing code, use
+ <seealso marker="#prepare_loading/1">prepare_loading/1</seealso>
+ and
+ <seealso marker="#finish_loading/1">finish_loading/1</seealso>
+ instead of <c>atomic_load/1</c>. Here is an example:</p>
+<pre>
+{ok,Prepared} = code:prepare_loading(Modules),
+%% Put the application into an inactive state or do any
+%% other preparation needed before changing the code.
+ok = code:finish_loading(Prepared),
+%% Resume the application.</pre>
+ </desc>
+ </func>
+ <func>
+ <name name="prepare_loading" arity="1"/>
+ <fsummary>Prepare a list of modules atomically</fsummary>
+ <desc>
+ <p>Prepares to load the modules in the list
+ <c><anno>Modules</anno></c>.
+ Finish the loading by calling
+ <seealso marker="#finish_loading/1">finish_loading(Prepared)</seealso>.</p>
+ <p>This function can fail with one of the following error reasons:</p>
+ <taglist>
+ <tag><c>badfile</c></tag>
+ <item>
+ <p>The object code has an incorrect format or the module
+ name in the object code is not the expected module name.</p>
+ </item>
+ <tag><c>nofile</c></tag>
+ <item>
+ <p>No file with object code exists.</p>
+ </item>
+ <tag><c>on_load_not_allowed</c></tag>
+ <item>
+ <p>A module contains an
+ <seealso marker="doc/reference_manual:code_loading#on_load">-on_load function</seealso>.</p>
+ </item>
+ <tag><c>duplicated</c></tag>
+ <item>
+ <p>A module is included more than once in
+ <c><anno>Modules</anno></c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name name="finish_loading" arity="1"/>
+ <fsummary>Finish loading a list of prepared modules atomically</fsummary>
+ <desc>
+ <p>Tries to load code for all modules that have been previously
+ prepared by
+ <seealso marker="#prepare_loading/1">prepare_loading/1</seealso>.
+ The loading occurs atomically, meaning that
+ either all modules are loaded at the same time, or
+ none of the modules are loaded.</p>
+ <p>This function can fail with one of the following error reasons:</p>
+ <taglist>
+ <tag><c>not_purged</c></tag>
+ <item>
+ <p>The object code can not be loaded because an old version
+ of the code already exists.</p>
+ </item>
+ <tag><c>sticky_directory</c></tag>
+ <item>
+ <p>The object code resides in a sticky directory.</p>
+ </item>
+ <tag><c>pending_on_load</c></tag>
+ <item>
+ <p>A previously loaded module contains an
+ <c>-on_load</c> function that never finished.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name name="ensure_modules_loaded" arity="1"/>
+ <fsummary>Ensure that a list of modules is loaded</fsummary>
+ <desc>
+ <p>Tries to load any modules not already loaded in the list
+ <c><anno>Modules</anno></c> in the same way as
+ <seealso marker="#load_file/1">load_file/1</seealso>.</p>
+ <p>Returns <c>ok</c> if successful, or
+ <c>{error,[{Module,Reason}]}</c> if loading of some modules fails.
+ See <seealso marker="#error_reasons">Error Reasons for Code-Loading Functions</seealso> for a description of other possible error reasons.</p>
+ </desc>
+ </func>
+ <func>
<name name="delete" arity="1"/>
<fsummary>Removes current code for a module</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml
index b9fad17ce1..9da4773f2d 100644
--- a/lib/kernel/doc/src/heart.xml
+++ b/lib/kernel/doc/src/heart.xml
@@ -118,6 +118,13 @@
<p>In the following descriptions, all function fails with reason
<c>badarg</c> if <c>heart</c> is not started.</p>
</description>
+
+ <datatypes>
+ <datatype>
+ <name name="heart_option"/>
+ </datatype>
+ </datatypes>
+
<funcs>
<func>
<name name="set_cmd" arity="1"/>
@@ -154,6 +161,62 @@
the empty string will be returned.</p>
</desc>
</func>
+
+ <func>
+ <name name="set_callback" arity="2"/>
+ <fsummary>Set a validation callback</fsummary>
+ <desc>
+ <p> This validation callback will be executed before any heartbeat sent
+ to the port program. For the validation to succeed it needs to return
+ with the value <c>ok</c>.
+ </p>
+ <p> An exception within the callback will be treated as a validation failure. </p>
+ <p> The callback will be removed if the system reboots. </p>
+ </desc>
+ </func>
+ <func>
+ <name name="clear_callback" arity="0"/>
+ <fsummary>Clear the validation callback</fsummary>
+ <desc>
+ <p>Removes the validation callback call before heartbeats.</p>
+ </desc>
+ </func>
+ <func>
+ <name name="get_callback" arity="0"/>
+ <fsummary>Get the validation callback</fsummary>
+ <desc>
+ <p>Get the validation callback. If the callback is cleared, <c>none</c> will be returned.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="set_options" arity="1"/>
+ <fsummary>Set a list of options</fsummary>
+ <desc>
+ <p> Valid options <c>set_options</c> are: </p>
+ <taglist>
+ <tag><c>check_schedulers</c></tag>
+ <item>
+ <p>If enabled, a signal will be sent to each scheduler to check its
+ responsiveness. The system check occurs before any heartbeat sent
+ to the port program. If any scheduler is not responsive enough the
+ heart program will not receive its heartbeat and thus eventually terminate the node.
+ </p>
+ </item>
+ </taglist>
+ <p> Returns with the value <c>ok</c> if the options are valid.</p>
+ </desc>
+ </func>
+ <func>
+ <name name="get_options" arity="0"/>
+ <fsummary>Get the temporary reboot command</fsummary>
+ <desc>
+ <p>Returns <c>{ok, Options}</c> where <c>Options</c> is a list of current options enabled for heart.
+ If the callback is cleared, <c>none</c> will be returned.</p>
+ </desc>
+ </func>
+
+
</funcs>
</erlref>
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 59e226df43..0882cb170c 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -28,11 +28,15 @@
get_path/0,
load_file/1,
ensure_loaded/1,
+ ensure_modules_loaded/1,
load_abs/1,
load_abs/2,
load_binary/3,
load_native_partial/2,
load_native_sticky/3,
+ atomic_load/1,
+ prepare_loading/1,
+ finish_loading/1,
delete/1,
purge/1,
soft_purge/1,
@@ -71,6 +75,7 @@
-deprecated({rehash,0,next_major_release}).
-export_type([load_error_rsn/0, load_ret/0]).
+-export_type([prepared_code/0]).
-include_lib("kernel/include/file.hrl").
@@ -88,6 +93,11 @@
-type loaded_ret_atoms() :: 'cover_compiled' | 'preloaded'.
-type loaded_filename() :: (Filename :: file:filename()) | loaded_ret_atoms().
+-define(PREPARED, '$prepared$').
+-opaque prepared_code() ::
+ {?PREPARED,[{module(),{binary(),string(),_}}]}.
+
+
%%% BIFs
-export([get_chunk/2, is_module_native/1, make_stub_module/3, module_md5/1]).
@@ -303,6 +313,313 @@ rehash() ->
-spec get_mode() -> 'embedded' | 'interactive'.
get_mode() -> call(get_mode).
+%%%
+%%% Loading of several modules in parallel.
+%%%
+
+-spec ensure_modules_loaded([Module]) ->
+ 'ok' | {'error',[{Module,What}]} when
+ Module :: module(),
+ What :: badfile | nofile | on_load_failure.
+
+ensure_modules_loaded(Modules) when is_list(Modules) ->
+ case prepare_ensure(Modules, []) of
+ Ms when is_list(Ms) ->
+ ensure_modules_loaded_1(Ms);
+ error ->
+ error(function_clause, [Modules])
+ end.
+
+ensure_modules_loaded_1(Ms0) ->
+ Ms = lists:usort(Ms0),
+ {Prep,Error0} = load_mods(Ms),
+ {OnLoad,Normal} = partition_on_load(Prep),
+ Error1 = case finish_loading(Normal, true) of
+ ok -> Error0;
+ {error,Err} -> Err ++ Error0
+ end,
+ ensure_modules_loaded_2(OnLoad, Error1).
+
+ensure_modules_loaded_2([{M,_}|Ms], Errors) ->
+ case ensure_loaded(M) of
+ {module,M} ->
+ ensure_modules_loaded_2(Ms, Errors);
+ {error,Err} ->
+ ensure_modules_loaded_2(Ms, [{M,Err}|Errors])
+ end;
+ensure_modules_loaded_2([], []) ->
+ ok;
+ensure_modules_loaded_2([], [_|_]=Errors) ->
+ {error,Errors}.
+
+prepare_ensure([M|Ms], Acc) when is_atom(M) ->
+ case erlang:module_loaded(M) of
+ true ->
+ prepare_ensure(Ms, Acc);
+ false ->
+ prepare_ensure(Ms, [M|Acc])
+ end;
+prepare_ensure([], Acc) ->
+ Acc;
+prepare_ensure(_, _) ->
+ error.
+
+-spec atomic_load(Modules) -> 'ok' | {'error',[{Module,What}]} when
+ Modules :: [Module | {Module, Filename, Binary}],
+ Module :: module(),
+ Filename :: file:filename(),
+ Binary :: binary(),
+ What :: 'badfile' | 'nofile' | 'on_load_not_allowed' | 'duplicated' |
+ 'not_purged' | 'sticky_directory' | 'pending_on_load'.
+
+atomic_load(Modules) ->
+ case do_prepare_loading(Modules) of
+ {ok,Prep} ->
+ finish_loading(Prep, false);
+ {error,_}=Error ->
+ Error;
+ badarg ->
+ error(function_clause, [Modules])
+ end.
+
+-spec prepare_loading(Modules) ->
+ {'ok',Prepared} | {'error',[{Module,What}]} when
+ Modules :: [Module | {Module, Filename, Binary}],
+ Module :: module(),
+ Filename :: file:filename(),
+ Binary :: binary(),
+ Prepared :: prepared_code(),
+ What :: 'badfile' | 'nofile' | 'on_load_not_allowed' | 'duplicated'.
+
+prepare_loading(Modules) ->
+ case do_prepare_loading(Modules) of
+ {ok,Prep} ->
+ {ok,{?PREPARED,Prep}};
+ {error,_}=Error ->
+ Error;
+ badarg ->
+ error(function_clause, [Modules])
+ end.
+
+-spec finish_loading(Prepared) -> 'ok' | {'error',[{Module,What}]} when
+ Prepared :: prepared_code(),
+ Module :: module(),
+ What :: 'not_purged' | 'sticky_directory' | 'pending_on_load'.
+
+finish_loading({?PREPARED,Prepared}=Arg) when is_list(Prepared) ->
+ case verify_prepared(Prepared) of
+ ok ->
+ finish_loading(Prepared, false);
+ error ->
+ error(function_clause, [Arg])
+ end.
+
+partition_load([Item|T], Bs, Ms) ->
+ case Item of
+ {M,File,Bin} when is_atom(M) andalso
+ is_list(File) andalso
+ is_binary(Bin) ->
+ partition_load(T, [Item|Bs], Ms);
+ M when is_atom(M) ->
+ partition_load(T, Bs, [Item|Ms]);
+ _ ->
+ error
+ end;
+partition_load([], Bs, Ms) ->
+ {Bs,Ms}.
+
+do_prepare_loading(Modules) ->
+ case partition_load(Modules, [], []) of
+ {ModBins,Ms} ->
+ case prepare_loading_1(ModBins, Ms) of
+ {error,_}=Error ->
+ Error;
+ Prep when is_list(Prep) ->
+ {ok,Prep}
+ end;
+ error ->
+ badarg
+ end.
+
+prepare_loading_1(ModBins, Ms) ->
+ %% erlang:finish_loading/1 *will* detect duplicates.
+ %% However, we want to detect all errors that can be detected
+ %% by only examining the input data before call the LastAction
+ %% fun.
+ case prepare_check_uniq(ModBins, Ms) of
+ ok ->
+ prepare_loading_2(ModBins, Ms);
+ Error ->
+ Error
+ end.
+
+prepare_loading_2(ModBins, Ms) ->
+ {Prep0,Error0} = load_bins(ModBins),
+ {Prep1,Error1} = load_mods(Ms),
+ case Error0 ++ Error1 of
+ [] ->
+ prepare_loading_3(Prep0 ++ Prep1);
+ [_|_]=Error ->
+ {error,Error}
+ end.
+
+prepare_loading_3(Prep) ->
+ case partition_on_load(Prep) of
+ {[_|_]=OnLoad,_} ->
+ Error = [{M,on_load_not_allowed} || {M,_} <- OnLoad],
+ {error,Error};
+ {[],_} ->
+ Prep
+ end.
+
+prepare_check_uniq([{M,_,_}|T], Ms) ->
+ prepare_check_uniq(T, [M|Ms]);
+prepare_check_uniq([], Ms) ->
+ prepare_check_uniq_1(lists:sort(Ms), []).
+
+prepare_check_uniq_1([M|[M|_]=Ms], Acc) ->
+ prepare_check_uniq_1(Ms, [{M,duplicated}|Acc]);
+prepare_check_uniq_1([_|Ms], Acc) ->
+ prepare_check_uniq_1(Ms, Acc);
+prepare_check_uniq_1([], []) ->
+ ok;
+prepare_check_uniq_1([], [_|_]=Errors) ->
+ {error,Errors}.
+
+partition_on_load(Prep) ->
+ P = fun({_,{Bin,_,_}}) ->
+ erlang:has_prepared_code_on_load(Bin)
+ end,
+ lists:partition(P, Prep).
+
+verify_prepared([{M,{Prep,Name,_Native}}|T])
+ when is_atom(M), is_binary(Prep), is_list(Name) ->
+ try erlang:has_prepared_code_on_load(Prep) of
+ false ->
+ verify_prepared(T);
+ _ ->
+ error
+ catch
+ error:_ ->
+ error
+ end;
+verify_prepared([]) ->
+ ok;
+verify_prepared(_) ->
+ error.
+
+finish_loading(Prepared0, EnsureLoaded) ->
+ Prepared = [{M,{Bin,File}} || {M,{Bin,File,_}} <- Prepared0],
+ Native0 = [{M,Code} || {M,{_,_,Code}} <- Prepared0,
+ Code =/= undefined],
+ case call({finish_loading,Prepared,EnsureLoaded}) of
+ ok ->
+ finish_loading_native(Native0);
+ {error,Errors}=E when EnsureLoaded ->
+ S0 = sofs:relation(Errors),
+ S1 = sofs:domain(S0),
+ R0 = sofs:relation(Native0),
+ R1 = sofs:drestriction(R0, S1),
+ Native = sofs:to_external(R1),
+ finish_loading_native(Native),
+ E;
+ {error,_}=E ->
+ E
+ end.
+
+finish_loading_native([{Mod,Code}|Ms]) ->
+ _ = load_native_partial(Mod, Code),
+ finish_loading_native(Ms);
+finish_loading_native([]) ->
+ ok.
+
+load_mods([]) ->
+ {[],[]};
+load_mods(Mods) ->
+ Path = get_path(),
+ F = prepare_loading_fun(),
+ {ok,{Succ,Error0}} = erl_prim_loader:get_modules(Mods, F, Path),
+ Error = [case E of
+ badfile -> {M,E};
+ _ -> {M,nofile}
+ end || {M,E} <- Error0],
+ {Succ,Error}.
+
+load_bins([]) ->
+ {[],[]};
+load_bins(BinItems) ->
+ F = prepare_loading_fun(),
+ do_par(F, BinItems).
+
+-type prep_fun_type() :: fun((module(), file:filename(), binary()) ->
+ {ok,_} | {error,_}).
+
+-spec prepare_loading_fun() -> prep_fun_type().
+
+prepare_loading_fun() ->
+ GetNative = get_native_fun(),
+ fun(Mod, FullName, Beam) ->
+ case erlang:prepare_loading(Mod, Beam) of
+ Prepared when is_binary(Prepared) ->
+ {ok,{Prepared,FullName,GetNative(Beam)}};
+ {error,_}=Error ->
+ Error
+ end
+ end.
+
+get_native_fun() ->
+ Architecture = erlang:system_info(hipe_architecture),
+ try hipe_unified_loader:chunk_name(Architecture) of
+ ChunkTag ->
+ fun(Beam) -> code:get_chunk(Beam, ChunkTag) end
+ catch _:_ ->
+ fun(_) -> undefined end
+ end.
+
+do_par(Fun, L) ->
+ {_,Ref} = spawn_monitor(do_par_fun(Fun, L)),
+ receive
+ {'DOWN',Ref,process,_,Res} ->
+ Res
+ end.
+
+-spec do_par_fun(prep_fun_type(), list()) -> fun(() -> no_return()).
+
+do_par_fun(Fun, L) ->
+ fun() ->
+ _ = [spawn_monitor(do_par_fun_2(Fun, Item)) ||
+ Item <- L],
+ exit(do_par_recv(length(L), [], []))
+ end.
+
+-spec do_par_fun_2(prep_fun_type(),
+ {module(),file:filename(),binary()}) ->
+ fun(() -> no_return()).
+
+do_par_fun_2(Fun, Item) ->
+ fun() ->
+ {Mod,Filename,Bin} = Item,
+ try Fun(Mod, Filename, Bin) of
+ {ok,Res} ->
+ exit({good,{Mod,Res}});
+ {error,Error} ->
+ exit({bad,{Mod,Error}})
+ catch
+ _:Error ->
+ exit({bad,{Mod,Error}})
+ end
+ end.
+
+do_par_recv(0, Good, Bad) ->
+ {Good,Bad};
+do_par_recv(N, Good, Bad) ->
+ receive
+ {'DOWN',_,process,_,{good,Res}} ->
+ do_par_recv(N-1, [Res|Good], Bad);
+ {'DOWN',_,process,_,{bad,Res}} ->
+ do_par_recv(N-1, Good, [Res|Bad])
+ end.
+
%%-----------------------------------------------------------------
call(Req) ->
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index b52def8777..6262407354 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -28,11 +28,10 @@
]).
-include_lib("kernel/include/file.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
-import(lists, [foreach/2]).
--define(ANY_NATIVE_CODE_LOADED, any_native_code_loaded).
-
-type on_load_item() :: {reference(),module(),file:name_all(),[pid()]}.
-record(state, {supervisor :: pid(),
@@ -90,8 +89,6 @@ init(Ref, Parent, [Root,Mode]) ->
namedb = init_namedb(Path),
mode = Mode},
- put(?ANY_NATIVE_CODE_LOADED, false),
-
Parent ! {Ref,{ok,self()}},
loop(State).
@@ -289,14 +286,14 @@ handle_call({load_binary,Mod,File,Bin}, Caller, S) when is_atom(Mod) ->
handle_call({load_native_partial,Mod,Bin}, {_From,_Tag}, S) ->
Architecture = erlang:system_info(hipe_architecture),
Result = (catch hipe_unified_loader:load(Mod, Bin, Architecture)),
- Status = hipe_result_to_status(Result),
+ Status = hipe_result_to_status(Result, S),
{reply,Status,S};
handle_call({load_native_sticky,Mod,Bin,WholeModule}, {_From,_Tag}, S) ->
Architecture = erlang:system_info(hipe_architecture),
Result = (catch hipe_unified_loader:load_module(Mod, Bin, WholeModule,
Architecture)),
- Status = hipe_result_to_status(Result),
+ Status = hipe_result_to_status(Result, S),
{reply,Status,S};
handle_call({ensure_loaded,Mod}, Caller, St) when is_atom(Mod) ->
@@ -356,6 +353,9 @@ handle_call({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}, {_From
handle_call(get_mode, {_From,_Tag}, S=#state{mode=Mode}) ->
{reply, Mode, S};
+handle_call({finish_loading,Prepared,EnsureLoaded}, {_,_}, S) ->
+ {reply,finish_loading(Prepared, EnsureLoaded, S),S};
+
handle_call(Other,{_From,_Tag}, S) ->
error_msg(" ** Codeserver*** ignoring ~w~n ",[Other]),
{noreply,S}.
@@ -1107,8 +1107,7 @@ try_load_module_2(File, Mod, Bin, Caller, Architecture,
#state{moddb=Db}=St) ->
case catch hipe_unified_loader:load_native_code(Mod, Bin, Architecture) of
{module,Mod} = Module ->
- put(?ANY_NATIVE_CODE_LOADED, true),
- ets:insert(Db, {Mod,File}),
+ ets:insert(Db, [{{native,Mod},true},{Mod,File}]),
{reply,Module,St};
no_native ->
try_load_module_3(File, Mod, Bin, Caller, Architecture, St);
@@ -1122,7 +1121,7 @@ try_load_module_3(File, Mod, Bin, Caller, Architecture,
case erlang:load_module(Mod, Bin) of
{module,Mod} = Module ->
ets:insert(Db, {Mod,File}),
- post_beam_load(Mod, Architecture),
+ post_beam_load([Mod], Architecture, St),
{reply,Module,St};
{error,on_load} ->
handle_on_load(Mod, File, Caller, St);
@@ -1131,23 +1130,24 @@ try_load_module_3(File, Mod, Bin, Caller, Architecture,
{reply,Error,St}
end.
-hipe_result_to_status(Result) ->
+hipe_result_to_status(Result, #state{moddb=Db}) ->
case Result of
- {module,_} ->
- put(?ANY_NATIVE_CODE_LOADED, true),
+ {module,Mod} ->
+ ets:insert(Db, [{{native,Mod},true}]),
Result;
_ ->
{error,Result}
end.
-post_beam_load(Mod, Architecture) ->
+post_beam_load(_, undefined, _) ->
+ %% HiPE is disabled.
+ ok;
+post_beam_load(Mods0, _Architecture, #state{moddb=Db}) ->
%% post_beam_load/2 can potentially be very expensive because it
- %% blocks multi-scheduling; thus we want to avoid the call if we
- %% know that it is not needed.
- case get(?ANY_NATIVE_CODE_LOADED) of
- true -> hipe_unified_loader:post_beam_load(Mod, Architecture);
- false -> ok
- end.
+ %% blocks multi-scheduling. Therefore, we only want to call
+ %% it with modules that are known to have native code loaded.
+ Mods = [M || M <- Mods0, ets:member(Db, {native,M})],
+ hipe_unified_loader:post_beam_load(Mods).
int_list([H|T]) when is_integer(H) -> int_list(T);
int_list([_|_]) -> false;
@@ -1221,7 +1221,6 @@ absname_vr([[X, $:]|Name], _, _AbsBase) ->
absname(filename:join(Name), Dcwd).
-
is_loaded(M, Db) ->
case ets:lookup(Db, M) of
[{M,File}] -> {file,File};
@@ -1236,6 +1235,64 @@ do_soft_purge(Mod) ->
erts_code_purger:soft_purge(Mod).
+%%%
+%%% Loading of multiple modules in parallel.
+%%%
+
+finish_loading(Prepared, EnsureLoaded, #state{moddb=Db}=St) ->
+ Ps = [fun(L) -> finish_loading_ensure(L, EnsureLoaded) end,
+ fun(L) -> abort_if_pending_on_load(L, St) end,
+ fun(L) -> abort_if_sticky(L, Db) end,
+ fun(L) -> do_finish_loading(L, St) end],
+ run(Ps, Prepared).
+
+finish_loading_ensure(Prepared, true) ->
+ {ok,[P || {M,_}=P <- Prepared, not erlang:module_loaded(M)]};
+finish_loading_ensure(Prepared, false) ->
+ {ok,Prepared}.
+
+abort_if_pending_on_load(L, #state{on_load=[]}) ->
+ {ok,L};
+abort_if_pending_on_load(L, #state{on_load=OnLoad}) ->
+ Pending = [{M,pending_on_load} ||
+ {M,_} <- L,
+ lists:keymember(M, 2, OnLoad)],
+ case Pending of
+ [] -> {ok,L};
+ [_|_] -> {error,Pending}
+ end.
+
+abort_if_sticky(L, Db) ->
+ Sticky = [{M,sticky_directory} || {M,_} <- L, is_sticky(M, Db)],
+ case Sticky of
+ [] -> {ok,L};
+ [_|_] -> {error,Sticky}
+ end.
+
+do_finish_loading(Prepared, #state{moddb=Db}=St) ->
+ MagicBins = [B || {_,{B,_}} <- Prepared],
+ case erlang:finish_loading(MagicBins) of
+ ok ->
+ MFs = [{M,F} || {M,{_,F}} <- Prepared],
+ true = ets:insert(Db, MFs),
+ Ms = [M || {M,_} <- MFs],
+ Architecture = erlang:system_info(hipe_architecture),
+ post_beam_load(Ms, Architecture, St),
+ ok;
+ {Reason,Ms} ->
+ {error,[{M,Reason} || M <- Ms]}
+ end.
+
+run([F], Data) ->
+ F(Data);
+run([F|Fs], Data0) ->
+ case F(Data0) of
+ {ok,Data} ->
+ run(Fs, Data);
+ {error,_}=Error ->
+ Error
+ end.
+
%% -------------------------------------------------------
%% The on_load functionality.
%% -------------------------------------------------------
@@ -1317,18 +1374,8 @@ finish_on_load_report(Mod, Term) ->
%% -------------------------------------------------------
all_loaded(Db) ->
- all_l(Db, ets:slot(Db, 0), 1, []).
-
-all_l(_Db, '$end_of_table', _, Acc) ->
- Acc;
-all_l(Db, ModInfo, N, Acc) ->
- NewAcc = strip_mod_info(ModInfo,Acc),
- all_l(Db, ets:slot(Db, N), N + 1, NewAcc).
-
-
-strip_mod_info([{{sticky,_},_}|T], Acc) -> strip_mod_info(T, Acc);
-strip_mod_info([H|T], Acc) -> strip_mod_info(T, [H|Acc]);
-strip_mod_info([], Acc) -> Acc.
+ Ms = ets:fun2ms(fun({M,_}=T) when is_atom(M) -> T end),
+ ets:select(Db, Ms).
-spec error_msg(io:format(), [term()]) -> 'ok'.
error_msg(Format, Args) ->
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index 55ce9a7e64..c6202dd796 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -32,7 +32,7 @@
%% External exports
-export([start/0, start_link/0, stop/0, port_please/2,
port_please/3, names/0, names/1,
- register_node/2, open/0, open/1, open/2]).
+ register_node/2, register_node/3, open/0, open/1, open/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -102,7 +102,9 @@ names(EpmdAddr) ->
register_node(Name, PortNo) ->
- gen_server:call(erl_epmd, {register, Name, PortNo}, infinity).
+ register_node(Name, PortNo, inet).
+register_node(Name, PortNo, Family) ->
+ gen_server:call(erl_epmd, {register, Name, PortNo, Family}, infinity).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
@@ -120,10 +122,10 @@ init(_) ->
-spec handle_call(calls(), term(), state()) ->
{'reply', term(), state()} | {'stop', 'shutdown', 'ok', state()}.
-handle_call({register, Name, PortNo}, _From, State) ->
+handle_call({register, Name, PortNo, Family}, _From, State) ->
case State#state.socket of
P when P < 0 ->
- case do_register_node(Name, PortNo) of
+ case do_register_node(Name, PortNo, Family) of
{alive, Socket, Creation} ->
S = State#state{socket = Socket,
port_no = PortNo,
@@ -206,8 +208,12 @@ open({A,B,C,D,E,F,G,H}=EpmdAddr, Timeout) when ?ip6(A,B,C,D,E,F,G,H) ->
close(Socket) ->
gen_tcp:close(Socket).
-do_register_node(NodeName, TcpPort) ->
- case open() of
+do_register_node(NodeName, TcpPort, Family) ->
+ Localhost = case Family of
+ inet -> open({127,0,0,1});
+ inet6 -> open({0,0,0,0,0,0,0,1})
+ end,
+ case Localhost of
{ok, Socket} ->
Name = to_string(NodeName),
Extra = "",
diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl
index d7dba4ac80..8cb2a725e8 100644
--- a/lib/kernel/src/gen_tcp.erl
+++ b/lib/kernel/src/gen_tcp.erl
@@ -114,7 +114,8 @@
option().
-type socket() :: port().
--export_type([option/0, option_name/0, connect_option/0, listen_option/0]).
+-export_type([option/0, option_name/0, connect_option/0, listen_option/0,
+ socket/0]).
%%
%% Connect a socket
diff --git a/lib/kernel/src/heart.erl b/lib/kernel/src/heart.erl
index 137fad706f..eea78aabdf 100644
--- a/lib/kernel/src/heart.erl
+++ b/lib/kernel/src/heart.erl
@@ -34,7 +34,11 @@
%%%
%%% It recognizes the flag '-heart'
%%%--------------------------------------------------------------------
--export([start/0, init/2, set_cmd/1, clear_cmd/0, get_cmd/0, cycle/0]).
+-export([start/0, init/2,
+ set_cmd/1, clear_cmd/0, get_cmd/0,
+ set_callback/2, clear_callback/0, get_callback/0,
+ set_options/1, get_options/0,
+ cycle/0]).
-define(START_ACK, 1).
-define(HEART_BEAT, 2).
@@ -49,6 +53,16 @@
-define(CYCLE_TIMEOUT, 10000).
-define(HEART_PORT_NAME, heart_port).
+%% valid heart options
+-define(SCHEDULER_CHECK_OPT, check_schedulers).
+
+-type heart_option() :: ?SCHEDULER_CHECK_OPT.
+
+-record(state,{port :: port(),
+ cmd :: [] | binary(),
+ options :: [heart_option()],
+ callback :: 'undefined' | {atom(), atom()}}).
+
%%---------------------------------------------------------------------
-spec start() -> 'ignore' | {'error', term()} | {'ok', pid()}.
@@ -81,11 +95,11 @@ wait_for_init_ack(From) ->
init(Starter, Parent) ->
process_flag(trap_exit, true),
process_flag(priority, max),
- register(heart, self()),
+ register(?MODULE, self()),
case catch start_portprogram() of
{ok, Port} ->
Starter ! {ok, self()},
- loop(Parent, Port, "");
+ loop(Parent, #state{port=Port, cmd=[], options=[]});
no_heart ->
Starter ! {no_heart, self()};
error ->
@@ -96,33 +110,68 @@ init(Starter, Parent) ->
Cmd :: string().
set_cmd(Cmd) ->
- heart ! {self(), set_cmd, Cmd},
+ ?MODULE ! {self(), set_cmd, Cmd},
wait().
-spec get_cmd() -> {ok, Cmd} when
Cmd :: string().
get_cmd() ->
- heart ! {self(), get_cmd},
+ ?MODULE ! {self(), get_cmd},
wait().
-spec clear_cmd() -> ok.
clear_cmd() ->
- heart ! {self(), clear_cmd},
+ ?MODULE ! {self(), clear_cmd},
+ wait().
+
+-spec set_callback(Module,Function) -> 'ok' | {'error', {'bad_callback', {Module, Function}}} when
+ Module :: atom(),
+ Function :: atom().
+
+set_callback(Module, Function) ->
+ ?MODULE ! {self(), set_callback, {Module,Function}},
+ wait().
+
+-spec get_callback() -> {'ok', {Module, Function}} | 'none' when
+ Module :: atom(),
+ Function :: atom().
+
+get_callback() ->
+ ?MODULE ! {self(), get_callback},
+ wait().
+
+-spec clear_callback() -> ok.
+
+clear_callback() ->
+ ?MODULE ! {self(), clear_callback},
+ wait().
+
+-spec set_options(Options) -> 'ok' | {'error', {'bad_options', Options}} when
+ Options :: [heart_option()].
+
+set_options(Options) ->
+ ?MODULE ! {self(), set_options, Options},
wait().
+-spec get_options() -> {'ok', Options} | 'none' when
+ Options :: [atom()].
+
+get_options() ->
+ ?MODULE ! {self(), get_options},
+ wait().
%%% Should be used solely by the release handler!!!!!!!
-spec cycle() -> 'ok' | {'error', term()}.
cycle() ->
- heart ! {self(), cycle},
+ ?MODULE ! {self(), cycle},
wait().
wait() ->
receive
- {heart, Res} ->
+ {?MODULE, Res} ->
Res
end.
@@ -182,8 +231,8 @@ wait_ack(Port) ->
{error, Reason}
end.
-loop(Parent, Port, Cmd) ->
- _ = send_heart_beat(Port),
+loop(Parent, #state{port=Port}=S) ->
+ _ = send_heart_beat(S),
receive
{From, set_cmd, NewCmd0} ->
Enc = file:native_name_encoding(),
@@ -191,37 +240,72 @@ loop(Parent, Port, Cmd) ->
NewCmd when is_binary(NewCmd), byte_size(NewCmd) < 2047 ->
_ = send_heart_cmd(Port, NewCmd),
_ = wait_ack(Port),
- From ! {heart, ok},
- loop(Parent, Port, NewCmd);
+ From ! {?MODULE, ok},
+ loop(Parent, S#state{cmd=NewCmd});
_ ->
- From ! {heart, {error, {bad_cmd, NewCmd0}}},
- loop(Parent, Port, Cmd)
+ From ! {?MODULE, {error, {bad_cmd, NewCmd0}}},
+ loop(Parent, S)
end;
{From, clear_cmd} ->
- From ! {heart, ok},
- _ = send_heart_cmd(Port, ""),
+ From ! {?MODULE, ok},
+ _ = send_heart_cmd(Port, []),
_ = wait_ack(Port),
- loop(Parent, Port, "");
+ loop(Parent, S#state{cmd = []});
{From, get_cmd} ->
- From ! {heart, get_heart_cmd(Port)},
- loop(Parent, Port, Cmd);
+ From ! {?MODULE, get_heart_cmd(Port)},
+ loop(Parent, S);
+ {From, set_callback, Callback} ->
+ case Callback of
+ {M,F} when is_atom(M), is_atom(F) ->
+ From ! {?MODULE, ok},
+ loop(Parent, S#state{callback=Callback});
+ _ ->
+ From ! {?MODULE, {error, {bad_callback, Callback}}},
+ loop(Parent, S)
+ end;
+ {From, get_callback} ->
+ Res = case S#state.callback of
+ undefined -> none;
+ Cb -> {ok, Cb}
+ end,
+ From ! {?MODULE, Res},
+ loop(Parent, S);
+ {From, clear_callback} ->
+ From ! {?MODULE, ok},
+ loop(Parent, S#state{callback=undefined});
+ {From, set_options, Options} ->
+ case validate_options(Options) of
+ Validated when is_list(Validated) ->
+ From ! {?MODULE, ok},
+ loop(Parent, S#state{options=Validated});
+ _ ->
+ From ! {?MODULE, {error, {bad_options, Options}}},
+ loop(Parent, S)
+ end;
+ {From, get_options} ->
+ Res = case S#state.options of
+ [] -> none;
+ Cb -> {ok, Cb}
+ end,
+ From ! {?MODULE, Res},
+ loop(Parent, S);
{From, cycle} ->
%% Calls back to loop
- do_cycle_port_program(From, Parent, Port, Cmd);
+ do_cycle_port_program(From, Parent, S);
{'EXIT', Parent, shutdown} ->
no_reboot_shutdown(Port);
{'EXIT', Parent, Reason} ->
exit(Port, Reason),
exit(Reason);
{'EXIT', Port, badsig} -> % we can ignore badsig-messages!
- loop(Parent, Port, Cmd);
+ loop(Parent, S);
{'EXIT', Port, _Reason} ->
- exit({port_terminated, {heart, loop, [Parent, Port, Cmd]}});
+ exit({port_terminated, {?MODULE, loop, [Parent, S]}});
_ ->
- loop(Parent, Port, Cmd)
+ loop(Parent, S)
after
?TIMEOUT ->
- loop(Parent, Port, Cmd)
+ loop(Parent, S)
end.
-spec no_reboot_shutdown(port()) -> no_return().
@@ -233,36 +317,44 @@ no_reboot_shutdown(Port) ->
exit(normal)
end.
-do_cycle_port_program(Caller, Parent, Port, Cmd) ->
+validate_options(Opts) -> validate_options(Opts,[]).
+validate_options([],Res) -> Res;
+validate_options([?SCHEDULER_CHECK_OPT=Opt|Opts],Res) -> validate_options(Opts,[Opt|Res]);
+validate_options(_,_) -> error.
+
+do_cycle_port_program(Caller, Parent, #state{port=Port} = S) ->
unregister(?HEART_PORT_NAME),
case catch start_portprogram() of
{ok, NewPort} ->
_ = send_shutdown(Port),
receive
{'EXIT', Port, _Reason} ->
- _ = send_heart_cmd(NewPort, Cmd),
- Caller ! {heart, ok},
- loop(Parent, NewPort, Cmd)
+ _ = send_heart_cmd(NewPort, S#state.cmd),
+ Caller ! {?MODULE, ok},
+ loop(Parent, S#state{port=NewPort})
after
?CYCLE_TIMEOUT ->
%% Huh! Two heart port programs running...
%% well, the old one has to be sick not to respond
%% so we'll settle for the new one...
- _ = send_heart_cmd(NewPort, Cmd),
- Caller ! {heart, {error, stop_error}},
- loop(Parent, NewPort, Cmd)
+ _ = send_heart_cmd(NewPort, S#state.cmd),
+ Caller ! {?MODULE, {error, stop_error}},
+ loop(Parent, S#state{port=NewPort})
end;
no_heart ->
- Caller ! {heart, {error, no_heart}},
- loop(Parent, Port, Cmd);
+ Caller ! {?MODULE, {error, no_heart}},
+ loop(Parent, S);
error ->
- Caller ! {heart, {error, start_error}},
- loop(Parent, Port, Cmd)
+ Caller ! {?MODULE, {error, start_error}},
+ loop(Parent, S)
end.
%% "Beates" the heart once.
-send_heart_beat(Port) -> Port ! {self(), {command, [?HEART_BEAT]}}.
+send_heart_beat(#state{port=Port, callback=Cb, options=Opts}) ->
+ ok = check_system(Opts),
+ ok = check_callback(Cb),
+ Port ! {self(), {command, [?HEART_BEAT]}}.
%% Set a new HEART_COMMAND.
-dialyzer({no_improper_lists, send_heart_cmd/2}).
@@ -278,6 +370,24 @@ get_heart_cmd(Port) ->
{ok, Cmd}
end.
+check_system([]) -> ok;
+check_system([?SCHEDULER_CHECK_OPT|Opts]) ->
+ ok = erts_internal:system_check(schedulers),
+ check_system(Opts).
+
+%% validate system by performing a check before the heartbeat
+%% return 'ok' if everything is alright.
+%% Terminate if with reason if something is a miss.
+%% It is fine to timeout in the callback, in fact that is the intention
+%% if something goes wront -> no heartbeat.
+
+check_callback(Callback) ->
+ case Callback of
+ undefined -> ok;
+ {M,F} ->
+ erlang:apply(M,F,[])
+ end.
+
%% Sends shutdown command to the port.
send_shutdown(Port) -> Port ! {self(), {command, [?SHUT_DOWN]}}.
diff --git a/lib/kernel/src/hipe_unified_loader.erl b/lib/kernel/src/hipe_unified_loader.erl
index ddbbc548dd..73fcb2469c 100644
--- a/lib/kernel/src/hipe_unified_loader.erl
+++ b/lib/kernel/src/hipe_unified_loader.erl
@@ -44,7 +44,7 @@
-export([chunk_name/1,
%% Only the code and code_server modules may call the entries below!
load_native_code/3,
- post_beam_load/2,
+ post_beam_load/1,
load_module/4,
load/3]).
@@ -120,15 +120,15 @@ load_native_code(Mod, Bin, Architecture) when is_atom(Mod), is_binary(Bin) ->
%%========================================================================
--spec post_beam_load(atom(), hipe_architecture()) -> 'ok'.
+-spec post_beam_load([module()]) -> 'ok'.
-%% does nothing on a hipe-disabled system
-post_beam_load(_Mod, undefined) ->
+post_beam_load([])->
ok;
-post_beam_load(Mod, _) when is_atom(Mod) ->
+post_beam_load([_|_]=Mods) ->
erlang:system_flag(multi_scheduling, block),
try
- patch_to_emu(Mod)
+ _ = [patch_to_emu(Mod) || Mod <- Mods],
+ ok
after
erlang:system_flag(multi_scheduling, unblock)
end,
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index 860d3640d0..cc9e6f771a 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -18,9 +18,9 @@
%% %CopyrightEnd%
{"%VSN%",
%% Up from - max one major revision back
- [{<<"4\\.[0-1](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-18.*
+ [{<<"4\\.[0-2](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-18.*
{<<"3\\.[0-2](\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-17
%% Down to - max one major revision back
- [{<<"4\\.[0-1](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-18.*
+ [{<<"4\\.[0-2](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-18.*
{<<"3\\.[0-2](\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-17
}.
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index ba40dd3168..7b233741e0 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -79,7 +79,8 @@ MODULES= \
zlib_SUITE \
loose_node \
sendfile_SUITE \
- standard_error_SUITE
+ standard_error_SUITE \
+ multi_load_SUITE
APP_FILES = \
appinc.app \
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index cf8863fcd0..00f29aa8ed 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -798,7 +798,7 @@ analyse2(MFA={_,_,_}, Path, Visited0) ->
analyse(FL, [MFA|Path], my_usort([MFA|Visited0]), 0).
%%%% We need to check these manually...
-% fun's are ok as long as they are defined locally.
+%% fun's are ok as long as they are defined locally.
check_funs({'$M_EXPR','$F_EXPR',_},
[{unicode,characters_to_binary_int,3},
{unicode,characters_to_binary,3},
@@ -870,6 +870,8 @@ check_funs({'$M_EXPR','$F_EXPR',1},
{hipe_unified_loader,get_refs_from,2}| _]) -> 0;
check_funs({'$M_EXPR',warning_msg,2},
[{code_server,finish_on_load_report,2} | _]) -> 0;
+check_funs({'$M_EXPR','$F_EXPR',1},
+ [{code_server,run,2}|_]) -> 0;
%% This is cheating! /raimo
%%
%% check_funs(This = {M,_,_}, Path) ->
diff --git a/lib/kernel/test/erl_prim_loader_SUITE.erl b/lib/kernel/test/erl_prim_loader_SUITE.erl
index bc14a2764d..bccca59b93 100644
--- a/lib/kernel/test/erl_prim_loader_SUITE.erl
+++ b/lib/kernel/test/erl_prim_loader_SUITE.erl
@@ -29,7 +29,8 @@
inet_existing/1, inet_coming_up/1, inet_disconnects/1,
multiple_slaves/1, file_requests/1,
local_archive/1, remote_archive/1,
- primary_archive/1, virtual_dir_in_archive/1]).
+ primary_archive/1, virtual_dir_in_archive/1,
+ get_modules/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -44,7 +45,8 @@ all() ->
normalize_and_backslash, inet_existing,
inet_coming_up, inet_disconnects, multiple_slaves,
file_requests, local_archive, remote_archive,
- primary_archive, virtual_dir_in_archive].
+ primary_archive, virtual_dir_in_archive,
+ get_modules].
groups() ->
[].
@@ -109,6 +111,37 @@ get_file(Config) when is_list(Config) ->
?line error = erl_prim_loader:get_file({dummy}),
ok.
+get_modules(_Config) ->
+ MsGood = lists:sort([lists,gen_server,gb_trees,code_server]),
+ Ms = [certainly_not_existing|MsGood],
+ SuccExp = [begin
+ F = code:which(M),
+ {ok,Code} = file:read_file(F),
+ {M,{F,erlang:md5(Code)}}
+ end || M <- MsGood],
+ FailExp = [{certainly_not_existing,enoent}],
+
+ Path = code:get_path(),
+ Process = fun(_, F, Code) -> {ok,{F,erlang:md5(Code)}} end,
+ {ok,{Succ,FailExp}} = erl_prim_loader:get_modules(Ms, Process, Path),
+ SuccExp = lists:sort(Succ),
+
+ Name = inet_get_modules,
+ {ok, Node, BootPid} = complete_start_node(Name),
+ ThisDir = filename:dirname(code:which(?MODULE)),
+ true = rpc:call(Node, code, add_patha, [ThisDir]),
+ _ = rpc:call(Node, code, ensure_loaded, [?MODULE]),
+ {ok,{InetSucc,FailExp}} = rpc:call(Node, erl_prim_loader,
+ get_modules, [Ms,Process,Path]),
+ SuccExp = lists:sort(InetSucc),
+
+ stop_node(Node),
+ unlink(BootPid),
+ exit(BootPid, kill),
+
+ ok.
+
+
normalize_and_backslash(Config) ->
%% Test OTP-11170
case os:type() of
diff --git a/lib/kernel/test/heart_SUITE.erl b/lib/kernel/test/heart_SUITE.erl
index 4447475833..eb6cb06622 100644
--- a/lib/kernel/test/heart_SUITE.erl
+++ b/lib/kernel/test/heart_SUITE.erl
@@ -27,6 +27,8 @@
node_start_immediately_after_crash/1,
node_start_soon_after_crash/1,
set_cmd/1, clear_cmd/1, get_cmd/1,
+ callback_api/1,
+ options_api/1,
dont_drop/1, kill_pid/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -66,6 +68,8 @@ all() -> [
node_start_immediately_after_crash,
node_start_soon_after_crash,
set_cmd, clear_cmd, get_cmd,
+ callback_api,
+ options_api,
kill_pid
].
@@ -358,6 +362,69 @@ get_cmd(Config) when is_list(Config) ->
stop_node(Node),
ok.
+callback_api(Config) when is_list(Config) ->
+ {ok, Node} = start_check(slave, heart_test),
+ none = rpc:call(Node, heart, get_callback, []),
+ M0 = self(),
+ F0 = ok,
+ {error, {bad_callback, {M0,F0}}} = rpc:call(Node, heart, set_callback, [M0,F0]),
+ none = rpc:call(Node, heart, get_callback, []),
+ M1 = lists:duplicate(28, $a),
+ F1 = lists:duplicate(28, $b),
+ {error, {bad_callback, {M1,F1}}} = rpc:call(Node, heart, set_callback, [M1,F1]),
+ none = rpc:call(Node, heart, get_callback, []),
+
+ M2 = heart_check_module,
+ F2 = cb_ok,
+ F3 = cb_error,
+ Code0 = generate(M2, [], [
+ atom_to_list(F2) ++ "() -> ok.",
+ atom_to_list(F3) ++ "() -> exit(\"callback_error (as intended)\")."
+ ]),
+ {module, M2} = rpc:call(Node, erlang, load_module, [M2, Code0]),
+ ok = rpc:call(Node, M2, F2, []),
+ ok = rpc:call(Node, heart, set_callback, [M2,F2]),
+ {ok, {M2,F2}} = rpc:call(Node, heart, get_callback, []),
+ ok = rpc:call(Node, heart, clear_callback, []),
+ none = rpc:call(Node, heart, get_callback, []),
+ ok = rpc:call(Node, heart, set_callback, [M2,F2]),
+ {ok, {M2,F2}} = rpc:call(Node, heart, get_callback, []),
+ ok = rpc:call(Node, heart, set_callback, [M2,F3]),
+ receive {nodedown, Node} -> ok
+ after 5000 -> test_server:fail(node_not_killed)
+ end,
+ stop_node(Node),
+ ok.
+
+options_api(Config) when is_list(Config) ->
+ {ok, Node} = start_check(slave, heart_test),
+ none = rpc:call(Node, heart, get_options, []),
+ M0 = self(),
+ F0 = ok,
+ {error, {bad_options, {M0,F0}}} = rpc:call(Node, heart, set_options, [{M0,F0}]),
+ none = rpc:call(Node, heart, get_options, []),
+ Ls = lists:duplicate(28, $b),
+ {error, {bad_options, Ls}} = rpc:call(Node, heart, set_options, [Ls]),
+ none = rpc:call(Node, heart, get_options, []),
+
+ ok = rpc:call(Node, heart, set_options, [[check_schedulers]]),
+ {ok, [check_schedulers]} = rpc:call(Node, heart, get_options, []),
+ ok = rpc:call(Node, heart, set_options, [[]]),
+ none = rpc:call(Node, heart, get_options, []),
+
+ ok = rpc:call(Node, heart, set_options, [[check_schedulers]]),
+ {ok, [check_schedulers]} = rpc:call(Node, heart, get_options, []),
+ {error, {bad_options, Ls}} = rpc:call(Node, heart, set_options, [Ls]),
+ {ok, [check_schedulers]} = rpc:call(Node, heart, get_options, []),
+
+ receive after 3000 -> ok end, %% wait 3 secs
+
+ ok = rpc:call(Node, heart, set_options, [[]]),
+ none = rpc:call(Node, heart, get_options, []),
+ stop_node(Node),
+ ok.
+
+
dont_drop(suite) ->
%%% Removed as it may crash epmd/distribution in colourful
%%% ways. While we ARE finding out WHY, it would
diff --git a/lib/kernel/test/multi_load_SUITE.erl b/lib/kernel/test/multi_load_SUITE.erl
new file mode 100644
index 0000000000..bb87443e36
--- /dev/null
+++ b/lib/kernel/test/multi_load_SUITE.erl
@@ -0,0 +1,412 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2012. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(multi_load_SUITE).
+-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ basic_atomic_load/1,basic_errors/1,sticky_dir/1,
+ on_load_failing/1,ensure_modules_loaded/1,
+ native_code/1]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("syntax_tools/include/merl.hrl").
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [basic_atomic_load,basic_errors,sticky_dir,on_load_failing,
+ ensure_modules_loaded,native_code].
+
+groups() ->
+ [].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+basic_atomic_load(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, multi_load_sticky_dir),
+ _ = file:make_dir(Dir),
+
+ OldPath = code:get_path(),
+ try
+ code:add_patha(Dir),
+ do_basic(Dir)
+ after
+ code:set_path(OldPath)
+ end,
+
+ ok.
+
+do_basic(Dir) ->
+ MsVer1_0 = make_modules(5, versioned_module(1)),
+ MsVer1 = [{M,filename:absname(F, Dir),Bin} || {M,F,Bin} <- MsVer1_0],
+ _ = [ok = file:write_file(F, Bin) || {_,F,Bin} <- MsVer1],
+
+ Ms = [M || {M,_,_} <- MsVer1],
+ [] = [loaded || M <- Ms, is_loaded(M)],
+
+ ok = code:atomic_load(Ms),
+ _ = [1 = M:M() || M <- Ms],
+ _ = [F = code:which(M) || {M,F,_} <- MsVer1],
+ [] = [not_loaded || M <- Ms, not is_loaded(M)],
+
+ MsVer2 = update_modules(Ms, versioned_module(2)),
+ {ok,Prepared} = code:prepare_loading(MsVer2),
+ ok = code:finish_loading(Prepared),
+ _ = [2 = M:M() || M <- Ms],
+ _ = [F = code:which(M) || {M,F,_} <- MsVer2],
+ [] = [not_loaded || M <- Ms, not is_loaded(M)],
+
+ MsVer3 = update_modules(Ms, versioned_module(2)),
+ NotPurged = lists:sort([{M,not_purged} || M <- Ms]),
+ NotPurged = atomic_load_error(MsVer3, true),
+
+ ok.
+
+versioned_module(Ver) ->
+ fun(Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-export(['@Mod@'/0]).\n",
+ "'@Mod@'() -> _@Ver@.\n"])
+ end.
+
+basic_errors(_Config) ->
+ atomic_load_fc([42]),
+ atomic_load_fc([{"mod","file","bin"}]),
+
+ finish_loading_fc(atom),
+ {ok,{PrepTag,_}} = code:prepare_loading([code]),
+ finish_loading_fc({PrepTag,[x]}),
+ finish_loading_fc({PrepTag,[{m,{<<>>,"",<<>>}}]}),
+ Prep = prepared_with_wrong_magic_bin(),
+ finish_loading_fc(Prep),
+
+ [{x,badfile}] = atomic_load_error([{x,"x",<<"bad">>}], false),
+ [{a,badfile},{some_nonexistent_file,nofile}] =
+ atomic_load_error([some_nonexistent_file,{a,"a",<<>>}],
+ false),
+
+ %% Modules mentioned more than once.
+ Mods = make_modules(2, fun basic_module/1),
+ Ms = [M || {M,_,_} <- Mods],
+ DupMods = Mods ++ [mnesia] ++ Mods ++ [mnesia],
+ DupErrors0 = lists:sort([mnesia|Ms]),
+ DupErrors = [{M,duplicated} || M <- DupErrors0],
+ DupErrors = atomic_load_error(DupMods, false),
+
+ ok.
+
+atomic_load_fc(L) ->
+ {'EXIT',{function_clause,[{code,atomic_load,[L],_}|_]}} =
+ (catch code:atomic_load(L)),
+ {'EXIT',{function_clause,[{code,prepare_loading,[L],_}|_]}} =
+ (catch code:prepare_loading(L)).
+
+finish_loading_fc(Term) ->
+ {'EXIT',{function_clause,[{code,finish_loading,[Term],_}|_]}} =
+ (catch code:finish_loading(Term)).
+
+prepared_with_wrong_magic_bin() ->
+ {ok,Prep} = code:prepare_loading([?MODULE]),
+ prep_magic(Prep).
+
+prep_magic([H|T]) ->
+ [prep_magic(H)|prep_magic(T)];
+prep_magic(Tuple) when is_tuple(Tuple) ->
+ L = prep_magic(tuple_to_list(Tuple)),
+ list_to_tuple(L);
+prep_magic(Bin) when is_binary(Bin) ->
+ try erlang:has_prepared_code_on_load(Bin) of
+ false ->
+ %% Create a different kind of magic binary.
+ ets:match_spec_compile([{'_',[true],['$_']}])
+ catch
+ _:_ ->
+ Bin
+ end;
+prep_magic(Other) ->
+ Other.
+
+sticky_dir(_Config) ->
+ Mod0 = make_module(lists, fun basic_module/1),
+ Mod1 = make_module(gen_server, fun basic_module/1),
+ Ms = [Mod0,Mod1],
+ SD = sticky_directory,
+ StickyErrors = [{gen_server,SD},{lists,SD}],
+ StickyErrors = atomic_load_error(Ms, true),
+
+ ok.
+
+on_load_failing(_Config) ->
+ OnLoad = make_modules(1, fun on_load_module/1),
+ [{OnLoadMod,_,_}] = OnLoad,
+ Ms = make_modules(10, fun basic_module/1) ++ OnLoad,
+
+ %% Fail because there is a module with on_load in the list.
+ on_load_failure(OnLoadMod, Ms),
+ on_load_failure(OnLoadMod, [lists:last(Ms)]),
+
+ %% Fail because there already is a pending on_load.
+ [{HangingOnLoad,_,_}|_] = Ms,
+ spawn_hanging_on_load(HangingOnLoad),
+ NoOnLoadMs = lists:droplast(Ms),
+ {error,[{HangingOnLoad,pending_on_load}]} =
+ code:atomic_load(NoOnLoadMs),
+ hanging_on_load ! stop_hanging_and_unload,
+
+ ok.
+
+on_load_failure(OnLoadMod, Ms) ->
+ [{OnLoadMod,on_load_not_allowed}] = atomic_load_error(Ms, false).
+
+spawn_hanging_on_load(Mod) ->
+ {Mod,Name,Bin} = make_module(Mod, "unknown",
+ fun(_) ->
+ hanging_on_load_module(Mod)
+ end),
+ spawn_link(fun() ->
+ {error,on_load_failure} =
+ code:load_binary(Mod, Name, Bin)
+ end).
+
+hanging_on_load_module(Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-on_load(hang/0).\n",
+ "hang() ->\n"
+ " register(hanging_on_load, self()),\n"
+ " receive _ -> unload end.\n"]).
+
+ensure_modules_loaded(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, multi_load_ensure_modules_loaded),
+ _ = file:make_dir(Dir),
+
+ OldPath = code:get_path(),
+ try
+ code:add_patha(Dir),
+ do_ensure_modules_loaded(Dir)
+ after
+ code:set_path(OldPath)
+ end,
+
+ ok.
+
+do_ensure_modules_loaded(Dir) ->
+ %% Create a dummy "lists" module and place it in our code path.
+ {lists,ListsFile,ListsCode} = make_module(lists, fun basic_module/1),
+ ok = file:write_file(filename:absname(ListsFile, Dir), ListsCode),
+ {error,sticky_directory} = code:load_file(lists),
+
+ %% Make a new module that we can load.
+ Mod = make_module_file(Dir, fun basic_module/1),
+ false = is_loaded(Mod),
+
+ %% Make a new module with an on_load function.
+ OLMod = make_module_file(Dir, fun on_load_module/1),
+ false = is_loaded(OLMod),
+
+ %% lists should not be loaded again; Mod and OLMod should be
+ %% loaded. ?MODULE should not be reloaded, but there is no easy
+ %% way to test that. Repeating modules is OK.
+ ok = code:ensure_modules_loaded([?MODULE,lists,Mod,OLMod,
+ Mod,OLMod,Mod,lists]),
+ last = lists:last([last]),
+ true = is_loaded(Mod),
+ ok = Mod:Mod(),
+ true = is_loaded(OLMod),
+ _ = OLMod:module_info(),
+
+ %% Unload the modules that were loaded.
+ [begin
+ code:purge(M),
+ code:delete(M),
+ code:purge(M),
+ false = is_loaded(M)
+ end || M <- [Mod,OLMod]],
+
+ %% If there are some errors, all other modules should be loaded
+ %% anyway.
+ [{BadMod,BadFile,_}] = make_modules(1, fun basic_module/1),
+ ok = file:write_file(filename:absname(BadFile, Dir), <<"bad_code">>),
+ BadOLMod = make_module_file(Dir, fun failing_on_load_module/1),
+ BadEgg = bad__egg,
+ NativeMod = a_native_module,
+ NativeModFile = atom_to_list(NativeMod) ++ ".beam",
+ {NativeMod,_,NativeCode} = make_module(NativeMod, NativeModFile,
+ fun basic_module/1, [native]),
+ ok = file:write_file(filename:absname(NativeModFile, Dir), NativeCode),
+ ModulesToLoad = [OLMod,?MODULE,Mod,BadOLMod,NativeMod,
+ BadEgg,BadMod,lists],
+ {error,Error0} = code:ensure_modules_loaded(ModulesToLoad),
+ Error = lists:sort([{BadEgg,nofile},
+ {BadMod,badfile},
+ {BadOLMod,on_load_failure}]),
+ Error = lists:sort(Error0),
+ true = is_loaded(Mod),
+ true = is_loaded(OLMod),
+ true = is_loaded(NativeMod),
+ true = NativeMod:module_info(native),
+
+ ok.
+
+failing_on_load_module(Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-on_load(f/0).\n",
+ "f() -> fail.\n"]).
+
+native_code(_Config) ->
+ case erlang:system_info(hipe_architecture) of
+ undefined ->
+ {skip,"No native support"};
+ _ ->
+ do_native_code()
+ end.
+
+do_native_code() ->
+ CalledMod = native_called_module,
+ CallingMod = native_calling_module,
+
+ %% Create a module in native code that calls another module.
+ CallingMod = make_and_load(CallingMod,
+ calling_module_fun(CalledMod),
+ [native]),
+
+ %% Create a threaded-code module.
+ _ = make_and_load(CalledMod, called_module_fun(42), []),
+ 42 = CallingMod:call(),
+
+ %% Now replace it with a changed module in native code.
+ code:purge(CalledMod),
+ make_and_load(CalledMod, called_module_fun(43), [native]),
+ true = test_server:is_native(CalledMod),
+ 43 = CallingMod:call(),
+
+ %% Reload the called module and call it.
+ code:purge(CalledMod),
+ ModVer3 = make_module(CalledMod, "", called_module_fun(changed)),
+ ok = code:atomic_load([ModVer3]),
+ false = test_server:is_native(CalledMod),
+ changed = CallingMod:call(),
+ code:purge(CalledMod),
+
+ ok.
+
+make_and_load(Mod, Fun, Opts) ->
+ {Mod,_,Code} = make_module(Mod, "", Fun, Opts),
+ {module,Mod} = code:load_binary(Mod, "", Code),
+ Mod.
+
+calling_module_fun(Called) ->
+ fun(Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-export([call/0]).\n",
+ "call() -> _@Called@:f().\n"])
+ end.
+
+called_module_fun(Ret) ->
+ fun(Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-export([f/0]).\n",
+ "f() -> _@Ret@.\n"])
+ end.
+
+%%%
+%%% Common utilities
+%%%
+
+atomic_load_error(Modules, ErrorInFinishLoading) ->
+ {error,Errors0} = code:atomic_load(Modules),
+ {Errors1,Bool} =
+ case code:prepare_loading(Modules) of
+ {ok,Prepared} ->
+ {error,Es0} = code:finish_loading(Prepared),
+ {Es0,true};
+ {error,Es0} ->
+ {Es0,false}
+ end,
+ Errors = lists:sort(Errors0),
+ Errors = lists:sort(Errors1),
+ case {ErrorInFinishLoading,Bool} of
+ {B,B} ->
+ Errors;
+ {false,true} ->
+ ct:fail("LastAction fun must not be called");
+ {true,false} ->
+ ct:fail("LastAction fun was not called")
+ end.
+
+is_loaded(Mod) ->
+ case erlang:module_loaded(Mod) of
+ false ->
+ false = code:is_loaded(Mod);
+ true ->
+ {file,_} = code:is_loaded(Mod),
+ true
+ end.
+
+basic_module(Mod) ->
+ ?Q(["-module('@Mod@').\n"
+ "-export(['@Mod@'/0]).\n",
+ "'@Mod@'() -> ok."]).
+
+on_load_module(Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-on_load(f/0).\n",
+ "f() -> ok.\n"]).
+
+make_module_file(Dir, Fun) ->
+ [{Mod,File,Code}] = make_modules(1, Fun),
+ ok = file:write_file(filename:absname(File, Dir), Code),
+ Mod.
+
+make_modules(0, _) ->
+ [];
+make_modules(N, Fun) ->
+ U = erlang:unique_integer([positive]),
+ ModName = "m__" ++ integer_to_list(N) ++ "_" ++ integer_to_list(U),
+ Mod = list_to_atom(ModName),
+ ModItem = make_module(Mod, Fun),
+ [ModItem|make_modules(N-1, Fun)].
+
+update_modules(Ms, Fun) ->
+ [make_module(M, Fun) || M <- Ms].
+
+make_module(Mod, Fun) ->
+ Filename = atom_to_list(Mod) ++ ".beam",
+ make_module(Mod, Filename, Fun).
+
+make_module(Mod, Filename, Fun) ->
+ make_module(Mod, Filename, Fun, []).
+
+make_module(Mod, Filename, Fun, Opts) ->
+ Tree = Fun(Mod),
+ merl:print(Tree),
+ {ok,Mod,Code} = merl:compile(Tree, Opts),
+ {Mod,Filename,Code}.