File 4011-Do-not-attempt-to-start-children-of-running-apps.patch of Package erlang
From 27f61e9783773fc093bf9a77427d184f474385b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 1 Jul 2021 10:54:32 +0200
Subject: [PATCH] Do not attempt to start children of running apps
When optional applications were added in 79146711,
ensure_all_started/2 had to take explicit control
over the initialization cycle so available optional
applications were started properly. However, in
doing so, we lost one important optimization, which
was to not attempt to start children of a given
application if said application was already running.
This commit address this by exporting is_running/1
and skipping the work in case the given app is already
running. This change makes it so the time taken to
start a deep dependency tree goes from 58s to 4s.
---
lib/kernel/src/application.erl | 42 ++++++++++++++---------
lib/kernel/src/application_controller.erl | 34 +++++++++++++-----
2 files changed, 51 insertions(+), 25 deletions(-)
diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl
index c13263ee86..8efae6778f 100644
--- a/lib/kernel/src/application.erl
+++ b/lib/kernel/src/application.erl
@@ -139,23 +139,31 @@ ensure_all_started(Application, Type) ->
end.
ensure_all_started([App | Apps], OptionalApps, Type, Started) ->
- case ensure_loaded(App) of
- {ok, Name} ->
- case ensure_started(Name, App, Type, Started) of
- {ok, NewStarted} ->
- ensure_all_started(Apps, OptionalApps, Type, NewStarted);
- Error ->
- Error
- end;
- {error, {"no such file or directory", _} = Reason} ->
- case lists:member(App, OptionalApps) of
- true ->
- ensure_all_started(Apps, OptionalApps, Type, Started);
- false ->
- {error, {App, Reason}, Started}
- end;
- {error, Reason} ->
- {error, {App, Reason}, Started}
+ %% In case the app is already running, we just skip it instead
+ %% of attempting to start all of its children - which would
+ %% have already been loaded and started anyway.
+ case application_controller:is_running(App) of
+ false ->
+ case ensure_loaded(App) of
+ {ok, Name} ->
+ case ensure_started(Name, App, Type, Started) of
+ {ok, NewStarted} ->
+ ensure_all_started(Apps, OptionalApps, Type, NewStarted);
+ Error ->
+ Error
+ end;
+ {error, {"no such file or directory", _} = Reason} ->
+ case lists:member(App, OptionalApps) of
+ true ->
+ ensure_all_started(Apps, OptionalApps, Type, Started);
+ false ->
+ {error, {App, Reason}, Started}
+ end;
+ {error, Reason} ->
+ {error, {App, Reason}, Started}
+ end;
+ true ->
+ ensure_all_started(Apps, OptionalApps, Type, Started)
end;
ensure_all_started([], _OptionalApps, _Type, Started) ->
{ok, Started}.
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index 67c275c395..dea8ad9857 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -23,7 +23,7 @@
-export([start/1,
load_application/1, unload_application/1,
start_application/2, start_boot_application/2, stop_application/1,
- control_application/1,
+ control_application/1, is_running/1,
change_application_data/2, prep_config_change/0, config_change/1,
which_applications/0, which_applications/1,
loaded_applications/0, info/0, set_env/2,
@@ -237,6 +237,17 @@ unload_application(AppName) ->
start_application(AppName, RestartType) ->
gen_server:call(?AC, {start_application, AppName, RestartType}, infinity).
+%%-----------------------------------------------------------------
+%% Func: is_running/1
+%% Args: Application = atom()
+%% Purpose: Checks if an application is running.
+%% This is used by application to avoid traversing an
+%% application's child in case it is already running.
+%% Returns: boolean
+%%-----------------------------------------------------------------
+is_running(AppName) when is_atom(AppName) ->
+ gen_server:call(?AC, {is_running, AppName}, infinity).
+
%%-----------------------------------------------------------------
%% Func: start_boot_application/2
%% The same as start_application/2 expect that this function is
@@ -625,7 +636,7 @@ check_distributed(_Else) ->
{'noreply', state()} | {'reply', term(), state()}.
handle_call({load_application, Application}, From, S) ->
- case catch do_load_application(Application, S) of
+ case catch maybe_load_application(Application, S) of
{ok, NewS} ->
AppName = get_appl_name(Application),
case cntrl(AppName, S, {ac_load_application_req, AppName}) of
@@ -699,6 +710,10 @@ handle_call({start_application, AppName, RestartType}, From, S) ->
{noreply, SS}
end;
+handle_call({is_running, AppName}, _From, S) ->
+ #state{running = Running} = S,
+ {reply, lists:keymember(AppName, 1, Running), S};
+
handle_call({permit_application, AppName, Bool}, From, S) ->
Control = S#state.control,
Starting = S#state.starting,
@@ -1264,16 +1279,19 @@ get_loaded(App) ->
[{_Key, Appl}] -> {true, Appl};
_ -> false
end.
-
-do_load_application(Application, S) ->
+
+maybe_load_application(Application, S) ->
case get_loaded(Application) of
{true, _} ->
throw({error, {already_loaded, Application}});
false ->
- case make_appl(Application) of
- {ok, Appl} -> load(S, Appl);
- Error -> Error
- end
+ do_load_application(Application, S)
+ end.
+
+do_load_application(Application, S) ->
+ case make_appl(Application) of
+ {ok, Appl} -> load(S, Appl);
+ Error -> Error
end.
%% Recursively load the application and its included apps.
--
2.26.2