diff options
author | Fred Hebert <[email protected]> | 2013-06-20 13:56:56 -0400 |
---|---|---|
committer | Fred Hebert <[email protected]> | 2013-07-11 10:15:05 -0400 |
commit | 45a8f39ef1ade82a379b8a94ff3378d0b149bd96 (patch) | |
tree | 42455b8020ad0761a921e7cc60d79ccc5fb8de4e /lib | |
parent | 15874f5848bb4686b1ab1986adc2e8c366492a4d (diff) | |
download | otp-45a8f39ef1ade82a379b8a94ff3378d0b149bd96.tar.gz otp-45a8f39ef1ade82a379b8a94ff3378d0b149bd96.tar.bz2 otp-45a8f39ef1ade82a379b8a94ff3378d0b149bd96.zip |
Add application:ensure_all_started/1-2
This adds two new functions to the application module that will
recursively look for missing dependencies to start the entire dependency
chain of a given application, and then the application itself, if
possible.
Its entire behavior is based on the currently existing 'start/1-2'
functions, which will report dependencies that have not yet been started
and other problems encountered. 'ensure_all_started/1-2' will use this
information the way any programmer would manually do it to automate the
process.
This kind of functionality, while not OTP-ish (releases do this start
sequence for the user), is still useful for testing purposes, quick
demonstrations, and programmers who do not wish or know how to use OTP
releases.
The function returns '{ok, StartedApps}' for all started or already
started applications (already started are omitted from the list), and
will return '{error, {AppName, Reason}}' on a failure to allow users to
rapidly know why the function failed.
Note that no specific check is made for direct or indirect circular
dependencies between applications, and these will make the function go
in an infinite loop.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/kernel/doc/src/application.xml | 15 | ||||
-rw-r--r-- | lib/kernel/src/application.erl | 43 | ||||
-rw-r--r-- | lib/kernel/test/application_SUITE.erl | 107 |
3 files changed, 162 insertions, 3 deletions
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml index 362c373c6c..9c83a9af11 100644 --- a/lib/kernel/doc/src/application.xml +++ b/lib/kernel/doc/src/application.xml @@ -263,6 +263,21 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> + <name name="ensure_all_started" arity="1"/> + <name name="ensure_all_started" arity="2"/> + <fsummary>Load and start an application and its dependencies, recursively</fsummary> + <desc> + <p>Equivalent to calling <seealso marker="#start/2"><c>application:start/1,2</c></seealso> + repeatedly on all dependencies that have not yet been started for an application. + The function returns <c>{ok, AppNames}</c> for a successful start or for an already started + application (which are however omitted from the <c>AppNames</c> list), and reports + <c>{error, {AppName,Reason}}</c> for errors, where <c>Reason</c> is any possible reason + returned by <seealso marker="#start/2"><c>application:start/1,2</c> when starting a + specific dependency. In case of an error, the applications that were started by the + function are stopped to bring the set of running applications back to its initial state.</p> + </desc> + </func> + <func> <name name="start" arity="1"/> <name name="start" arity="2"/> <fsummary>Load and start an application</fsummary> diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl index 5dd6b73857..4e8ba1b78a 100644 --- a/lib/kernel/src/application.erl +++ b/lib/kernel/src/application.erl @@ -18,7 +18,8 @@ %% -module(application). --export([start/1, start/2, start_boot/1, start_boot/2, stop/1, +-export([ensure_all_started/1, ensure_all_started/2, start/1, start/2, + start_boot/1, start_boot/2, stop/1, load/1, load/2, unload/1, takeover/2, which_applications/0, which_applications/1, loaded_applications/0, permit/2]). @@ -113,6 +114,46 @@ load1(Application, DistNodes) -> unload(Application) -> application_controller:unload_application(Application). + +-spec ensure_all_started(Application) -> {'ok', Started} | {'error', Reason} when + Application :: atom(), + Started :: [atom()], + Reason :: term(). +ensure_all_started(Application) -> + ensure_all_started(Application, temporary). + +-spec ensure_all_started(Application, Type) -> {'ok', Started} | {'error', Reason} when + Application :: atom(), + Type :: restart_type(), + Started :: [atom()], + Reason :: term(). +ensure_all_started(Application, Type) -> + case ensure_all_started(Application, Type, []) of + {ok, Started} -> + {ok, lists:reverse(Started)}; + {error, Reason, Started} -> + [stop(App) || App <- Started], + {error, Reason} + end. + +ensure_all_started(Application, Type, Started) -> + case start(Application, Type) of + ok -> + {ok, [Application | Started]}; + {error, {already_started, Application}} -> + {ok, Started}; + {error, {not_started, Dependency}} -> + case ensure_all_started(Dependency, Type, Started) of + {ok, NewStarted} -> + ensure_all_started(Application, Type, NewStarted); + Error -> + Error + end; + {error, Reason} -> + {error, {Application, Reason}, Started} + end. + + -spec start(Application) -> 'ok' | {'error', Reason} when Application :: atom(), Reason :: term(). diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl index 1ff291be54..9ec8a15861 100644 --- a/lib/kernel/test/application_SUITE.erl +++ b/lib/kernel/test/application_SUITE.erl @@ -35,7 +35,7 @@ -export([config_change/1, distr_changed_tc1/1, distr_changed_tc2/1, - ensure_started/1, + ensure_started/1, ensure_all_started/1, shutdown_func/1, do_shutdown/1, shutdown_timeout/1]). -define(TESTCASE, testcase_name). @@ -52,7 +52,7 @@ all() -> [failover, failover_comp, permissions, load, load_use_cache, ensure_started, {group, reported_bugs}, start_phases, script_start, nodedown_start, permit_false_start_local, - permit_false_start_dist, get_key, get_env, + permit_false_start_dist, get_key, get_env, ensure_all_started, {group, distr_changed}, config_change, shutdown_func, shutdown_timeout]. groups() -> @@ -978,6 +978,85 @@ ensure_started(Conf) -> ok = application:unload(app1), ok. +ensure_all_started(suite) -> []; +ensure_all_started(doc) -> ["Test application:ensure_all_started/1-2."]; +ensure_all_started(Conf) -> + + {ok, Fd1} = file:open("app1.app", [write]), + w_app1(Fd1), + file:close(Fd1), + {ok, Fd9} = file:open("app9.app", [write]), + w_app9(Fd9), + file:close(Fd9), + {ok, Fd10} = file:open("app10.app", [write]), + w_app10_dep9(Fd10), + file:close(Fd10), + {ok, FdErr} = file:open("app_chain_error.app", [write]), + w_app(FdErr, app_chain_error()), + file:close(FdErr), + {ok, FdErr2} = file:open("app_chain_error2.app", [write]), + w_app(FdErr2, app_chain_error2()), + file:close(FdErr2), + + %% Single app start/stop + false = lists:keyfind(app1, 1, application:which_applications()), + {ok, [app1]} = application:ensure_all_started(app1), % app1 started + {app1, _, _} = lists:keyfind(app1, 1, application:which_applications()), + {ok, []} = application:ensure_all_started(app1), % no start needed + ok = application:stop(app1), + false = lists:keyfind(app1, 1, application:which_applications()), + ok = application:unload(app1), + + %% App or dependency not found. + Name = hopefully_not_an_existing_app_file, + {error,{Name, {"no such file or directory", _ }}} = + application:ensure_all_started(Name), + + %% Start dependencies. + {error, {not_started, app9}} = application:start(app10), + {ok, [app9,app10]} = application:ensure_all_started(app10, temporary), + {app9, _, _} = lists:keyfind(app9, 1, application:which_applications()), + {app10, _, _} = lists:keyfind(app10, 1, application:which_applications()), + %% Only report apps/dependencies that actually needed to start + ok = application:stop(app10), + ok = application:unload(app10), + {ok, [app10]} = application:ensure_all_started(app10, temporary), + ok = application:stop(app9), + ok = application:unload(app9), + ok = application:stop(app10), + ok = application:unload(app10), + + %% Deeper failure chain. We have the following dependencies: + %% app_chain_error -> app_chain_error2 + %% app_chain_error2 -> app10 + %% app_chain_error2 -> hopefully_not_an_existing_app + %% app10 -> app 9 + %% First we have none running and we expect to have neither app9 + %% nor app10 running after failing to start + %% hopefully_not_an_existing_app + {error, {hopefully_not_an_existing_app, {"no such file or directory", _}}}= + application:ensure_all_started(app_chain_error), + false = lists:keyfind(app9, 1, application:which_applications()), + false = lists:keyfind(app10, 1, application:which_applications()), + false = lists:keyfind(app_chain_error2,1,application:which_applications()), + false = lists:keyfind(app_chain_error, 1, application:which_applications()), + %% Here we will have app9 already running, and app10 should be + %% able to boot fine. + %% In this dependency failing, we expect app9 to still be running, but + %% not app10 after failing to start hopefully_not_an_existing_app + {ok, [app9]} = application:ensure_all_started(app9, temporary), + {error, {hopefully_not_an_existing_app, {"no such file or directory", _}}}= + application:ensure_all_started(app_chain_error), + {app9, _, _} = lists:keyfind(app9, 1, application:which_applications()), + false = lists:keyfind(app10, 1, application:which_applications()), + false = lists:keyfind(app_chain_error2,1,application:which_applications()), + false = lists:keyfind(app_chain_error, 1, application:which_applications()), + ok = application:stop(app9), + ok = application:unload(app9), + ok = application:unload(app10), + ok = application:unload(app_chain_error2), + ok = application:unload(app_chain_error), + ok. %%%----------------------------------------------------------------- %%% Testing of reported bugs and other tickets. @@ -2125,6 +2204,24 @@ app_start_error() -> {applications, [kernel]}, {mod, {app_start_error, []}}]}. +app_chain_error() -> + {application, app_chain_error, + [{description, "ERTS CXC 138 ce"}, + {vsn, "2.0"}, + {modules, []}, + {registered, []}, + {applications, [kernel, app_chain_error2]}, + {mod, {ch_sup, {app_chain_error, 20,20}}}]}. + +app_chain_error2() -> + {application, app_chain_error2, + [{description, "ERTS CXC 138 ce2"}, + {vsn, "2.0"}, + {modules, []}, + {registered, []}, + {applications, [kernel, app10, hopefully_not_an_existing_app]}, + {mod, {ch_sup, {app_chain_error2, 21,21}}}]}. + app_group_leader() -> {application, group_leader, [{description, "GROUP_LEADER CXC 138 11"}, @@ -2374,6 +2471,12 @@ w_app7(Fd) -> w_app8(Fd) -> io:format(Fd, "~p.\n", [app8()]). +w_app9(Fd) -> + io:format(Fd, "~p.\n", [app9()]). + +w_app10_dep9(Fd) -> + io:format(Fd, "~p.\n", [app10_dep9()]). + w_app_start_error(Fd) -> io:format(Fd, "~p.\n", [app_start_error()]). |