Skip to content

Commit

Permalink
Check for embedded mode in the client
Browse files Browse the repository at this point in the history
This ensures that checking for code:ensure_loaded/1
in embedded mode (and other operations) do not send
additional messages to the code server.

This pull request fully closes erlang#7889. While previous
work has fixed the slow path in the code server, this
pull request makes it so the code server is avoided
altogether.
  • Loading branch information
josevalim committed Jan 22, 2024
1 parent a09c2a5 commit 11b050e
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 39 deletions.
13 changes: 10 additions & 3 deletions lib/kernel/doc/src/code.xml
Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,11 @@ ok = code:finish_loading(Prepared),
<desc>
<p>Tries to load a module in the same way as
<seemfa marker="#load_file/1"><c>load_file/1</c></seemfa>,
unless the module is already loaded.
However, in embedded mode it does not load a module that is not
unless the module is already loaded. If called concurrently,
this function ensures only one process attempts to load said
module at a given time.</p>

<p>In embedded mode, it does not load a module that is not
already loaded, but returns <c>{error, embedded}</c> instead.
See <seeerl marker="#error_reasons">Error Reasons for Code-Loading Functions</seeerl> for a description of other possible error reasons.</p>
</desc>
Expand All @@ -614,7 +617,11 @@ ok = code:finish_loading(Prepared),
<desc>
<p>Tries to load any modules not already loaded in the list
<c><anno>Modules</anno></c> in the same way as
<seemfa marker="#load_file/1">load_file/1</seemfa>.</p>
<seemfa marker="#load_file/1">load_file/1</seemfa>.
Opposite to <seemfa marker="#ensure_loaded/1">ensure_loaded/1</seemfa>,
concurrent calls to this function will attempt to load
modules concurrently. Furthermore, modules are loaded
even in <c>embedded</c> mode.</p>
<p>Returns <c>ok</c> if successful, or
<c>{error,[{Module,Reason}]}</c> if loading of some modules fails.
See <seeerl marker="#error_reasons">Error Reasons for Code-Loading Functions</seeerl> for a description of other possible error reasons.</p>
Expand Down
29 changes: 17 additions & 12 deletions lib/kernel/src/code.erl
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,21 @@ ensure_loaded(Mod) when is_atom(Mod) ->
case erlang:module_loaded(Mod) of
true -> {module, Mod};
false ->
case call({get_object_code_for_loading, Mod}) of
{module, Mod} -> {module, Mod};
{error, What} -> {error, What};
{Binary,File,Ref} ->
case erlang:prepare_loading(Mod, Binary) of
{error,_}=Error ->
call({load_error, Ref, Mod, Error});
Prepared ->
call({load_module, Prepared, Mod, File, false, Ref})
end
case get_mode() of
interactive ->
case call({get_object_code_for_loading, Mod}) of
{module, Mod} -> {module, Mod};
{error, What} -> {error, What};
{Binary,File,Ref} ->
case erlang:prepare_loading(Mod, Binary) of
{error,_}=Error ->
call({load_error, Ref, Mod, Error});
Prepared ->
call({load_module, Prepared, Mod, File, false, Ref})
end
end;
embedded ->
{error, embedded}
end
end.

Expand Down Expand Up @@ -296,7 +301,7 @@ all_loaded() -> call(all_loaded).
Filename :: loaded_filename(),
Loaded :: boolean().
all_available() ->
case code:get_mode() of
case get_mode() of
interactive ->
all_available(get_path(), #{});
embedded ->
Expand Down Expand Up @@ -476,7 +481,7 @@ replace_path(Name, Dir, Cache) when (is_atom(Name) orelse is_list(Name)),
call({replace_path,Name,Dir,Cache}).

-spec get_mode() -> 'embedded' | 'interactive'.
get_mode() -> call(get_mode).
get_mode() -> code_server:get_mode().

-spec clear_cache() -> ok.
clear_cache() -> call(clear_cache).
Expand Down
39 changes: 15 additions & 24 deletions lib/kernel/src/code_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
%% This file holds the server part of the code_server.

-export([start_link/1,
call/1, absname/1,
call/1, absname/1, get_mode/0,
is_loaded/1, is_sticky/1,
system_code_change/4,
error_msg/2, info_msg/2
Expand All @@ -46,7 +46,6 @@
path :: [{file:name_all(), cache | nocache}],
moddb :: ets:table(),
namedb :: ets:table(),
mode = interactive :: 'interactive' | 'embedded',
on_load = [] :: [on_load_item()],
loading = #{} :: #{module() => [pid()]}}).
-type state() :: #state{}.
Expand All @@ -70,6 +69,9 @@ is_loaded(Mod) ->
is_sticky(Mod) ->
is_sticky(Mod, ?moddb).

get_mode() ->
persistent_term:get(?MODULE).

%% -----------------------------------------------------------
%% Init the code_server process.
%% -----------------------------------------------------------
Expand Down Expand Up @@ -104,9 +106,9 @@ init(Ref, Parent, [Root,Mode]) ->
root = Root,
path = Path,
moddb = Db,
namedb = create_namedb(Path, Root),
mode = Mode},
namedb = create_namedb(Path, Root)},

persistent_term:put(?MODULE, Mode),
Parent ! {Ref,{ok,self()}},
loop(State).

Expand Down Expand Up @@ -342,7 +344,7 @@ handle_call({get_object_code,Mod}, _From, St0) when is_atom(Mod) ->
handle_call({get_object_code_for_loading,Mod}, From, St0) when is_atom(Mod) ->
case erlang:module_loaded(Mod) of
true -> {reply, {module, Mod}, St0};
false when St0#state.mode =:= interactive ->
false ->
%% Handles pending on_load events first. If the code is being
%% loaded, finish before adding more entries to the queue.
Action = fun(_, St1) ->
Expand All @@ -351,16 +353,12 @@ handle_call({get_object_code_for_loading,Mod}, From, St0) when is_atom(Mod) ->
false -> get_object_code_for_loading(St1, Mod, From)
end
end,
handle_pending_on_load(Action, Mod, From, St0);
false -> {reply, {error,embedded}, St0}
handle_pending_on_load(Action, Mod, From, St0)
end;

handle_call(stop,_From, S) ->
{stop,normal,stopped,S};

handle_call(get_mode, _From, S=#state{mode=Mode}) ->
{reply, Mode, S};

handle_call({finish_loading,Prepared,EnsureLoaded}, _From, S) ->
{reply,finish_loading(Prepared, EnsureLoaded, S),S};

Expand Down Expand Up @@ -1115,20 +1113,13 @@ del_paths(_,Path,_) ->
{ok,Path}.

try_finish_module(File, Mod, PC, EnsureLoaded, From, St) ->
Action = case EnsureLoaded of
false ->
fun(_, S) -> try_finish_module_1(File, Mod, PC, From, false, S) end;
_ ->
fun(_, S0) ->
case erlang:module_loaded(Mod) of
true ->
reply_loading(EnsureLoaded, Mod, {module, Mod}, St);
false when S0#state.mode =:= interactive ->
try_finish_module_1(File, Mod, PC, From, EnsureLoaded, S0);
false ->
reply_loading(EnsureLoaded, Mod, {error, embedded}, St)
end
end
Action = fun(_, S) ->
case (EnsureLoaded =/= false) andalso erlang:module_loaded(Mod) of
true ->
reply_loading(EnsureLoaded, Mod, {module, Mod}, S);
false ->
try_finish_module_1(File, Mod, PC, From, EnsureLoaded, S)
end
end,
handle_pending_on_load(Action, Mod, From, St).

Expand Down

0 comments on commit 11b050e

Please sign in to comment.