summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Watson <watson.timothy@gmail.com>2013-11-21 21:59:55 +0000
committerTim Watson <watson.timothy@gmail.com>2013-11-21 21:59:55 +0000
commitac2bac239d424ab71c565d8137607e5cc1768d6f (patch)
tree6bc385538ff776abaf91a7900609c2af069701af
parent33cbee5b3799ef2b301cf513f6fef1103d26bc32 (diff)
downloadrabbitmq-server-git-ac2bac239d424ab71c565d8137607e5cc1768d6f.tar.gz
Re-work boot step handling
We need to support interleaving plugin boot steps with rabbit’s own (internal) ones during the boot/start sequence, and also running them independently during runtime activation. This commit also drops the use of an ets table to track which cleanup steps we’ve run, and simplifies the boot step execution procedures.
-rw-r--r--src/app_utils.erl16
-rw-r--r--src/rabbit.erl1
-rw-r--r--src/rabbit_boot.erl175
3 files changed, 146 insertions, 46 deletions
diff --git a/src/app_utils.erl b/src/app_utils.erl
index d08dfe6a5a..22e2df44bb 100644
--- a/src/app_utils.erl
+++ b/src/app_utils.erl
@@ -60,7 +60,7 @@ stop_applications(Apps) ->
start_applications(Apps, ErrorHandler) ->
manage_applications(fun lists:foldl/3,
- fun start/1,
+ fun application:start/1,
fun application:stop/1,
already_started,
ErrorHandler,
@@ -101,7 +101,7 @@ direct_dependencies(Root) ->
end,
sets:from_list([Root]),
digraph:out_edges(G, Root)),
- sets:to_list(sets:del_element(rabbit, Deps))
+ sets:to_list(Deps)
catch {graph_error, Reason} ->
{error, Reason}
after
@@ -132,14 +132,10 @@ app_dependency_order(RootApps, StripUnreachable) ->
%%---------------------------------------------------------------------------
%% Private API
-start(rabbit) ->
- case application:start(rabbit) of
- ok -> rabbit_boot:run_boot_steps(rabbit), ok;
- Err -> Err
- end;
-start(App) ->
- rabbit_boot:run_boot_steps(App),
- application:start(App).
+%% It might be worth documenting this on the plugin author's guide/page.
+%% A plugin should expect its boot steps to run /before/ the application
+%% is started, and its cleanup steps to run /after/ the application has
+%% fully stopped.
wait_for_application(Application) ->
case lists:keymember(Application, 1, rabbit_misc:which_applications()) of
diff --git a/src/rabbit.erl b/src/rabbit.erl
index 44cd275d3b..374fccc3a2 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -411,6 +411,7 @@ start(normal, []) ->
true = register(rabbit, self()),
print_banner(),
log_banner(),
+ rabbit_boot:run_boot_steps(),
{ok, SupPid};
Error ->
Error
diff --git a/src/rabbit_boot.erl b/src/rabbit_boot.erl
index af2bc9c406..e2174ddd9a 100644
--- a/src/rabbit_boot.erl
+++ b/src/rabbit_boot.erl
@@ -18,7 +18,7 @@
-export([boot_with/1, shutdown/1]).
-export([start/1, stop/1]).
--export([run_boot_steps/1]).
+-export([run_boot_steps/0]).
-export([boot_error/2, boot_error/4]).
-ifdef(use_specs).
@@ -27,7 +27,7 @@
-spec(shutdown/1 :: ([atom()]) -> 'ok').
-spec(start/1 :: ([atom()]) -> 'ok').
-spec(stop/1 :: ([atom()]) -> 'ok').
--spec(run_boot_steps/1 :: (atom()) -> 'ok').
+-spec(run_boot_steps/0 :: () -> 'ok').
-spec(boot_error/2 :: (term(), not_available | [tuple()]) -> no_return()).
-spec(boot_error/4 :: (term(), string(), [any()], not_available | [tuple()])
-> no_return()).
@@ -36,6 +36,31 @@
-define(BOOT_FILE, "boot.info").
+%%
+%% When the broker is starting, we must run all the boot steps within the
+%% rabbit:start/2 application callback, after rabbit_sup has started and
+%% before any plugin applications begin to start. To achieve this, we process
+%% the boot steps from all loaded applications and run them in dependent
+%% order.
+%%
+%% When the broker is already running, we must run all the boot steps for
+%% each application/plugin we're starting, plus any other (dependent) steps
+%% that have not been run. To achieve this, we process the boot steps from
+%% all loaded applications, but skip those that have already been run (i.e.,
+%% steps that have been run once whilst or since the broker started).
+%%
+%% Tracking which boot steps have already been run is done via an ets table.
+%% Because we frequently find ourselves needing to query this table without
+%% the rabbit application running (e.g., during the initial boot sequence
+%% and when we've "cold started" a node without any running applications),
+%% this table is serialized to a file after each operation. When the node is
+%% stopped cleanly, the file is deleted. When a node is in the process of
+%% starting, the file is also removed and replaced (since we do not want to
+%% track boot steps from a previous incarnation of the node).
+%%
+%% Cleanup steps...
+%%
+
%%---------------------------------------------------------------------------
%% Public API
@@ -43,6 +68,7 @@ boot_with(StartFun) ->
%% TODO: this should be done with monitors, not links, I think
Marker = spawn_link(fun() -> receive stop -> ok end end),
register(rabbit_boot, Marker),
+ ensure_boot_table(),
try
StartFun()
catch
@@ -73,8 +99,13 @@ shutdown(Apps) ->
start(Apps) ->
try
ensure_boot_table(),
- ok = app_utils:load_applications(Apps),
+ force_reload(Apps),
StartupApps = app_utils:app_dependency_order(Apps, false),
+ io:format("App Start Order: ~p~n", [StartupApps]),
+ case whereis(?MODULE) of
+ undefined -> run_boot_steps();
+ _ -> ok
+ end,
ok = app_utils:start_applications(StartupApps,
handle_app_error(could_not_start))
after
@@ -84,33 +115,71 @@ start(Apps) ->
stop(Apps) ->
ensure_boot_table(),
TargetApps =
- lists:usort(
- lists:append([app_utils:direct_dependencies(App) || App <- Apps])),
+ sets:to_list(
+ lists:foldl(
+ fun(App, Set) ->
+ lists:foldl(fun sets:add_element/2, Set,
+ app_utils:direct_dependencies(App) -- [rabbit])
+ end, sets:new(), Apps)),
+ io:format("Target Apps = ~p~n", [TargetApps]),
try
ok = app_utils:stop_applications(
TargetApps, handle_app_error(error_during_shutdown))
after
try
- [run_steps(App, rabbit_cleanup_step) || App <- TargetApps]
+ io:format("Running Cleanup~n"),
+ BootSteps = load_steps(rabbit_boot_step),
+ ToDelete = [Step || {App, _, _}=Step <- BootSteps,
+ lists:member(App, TargetApps)],
+ io:format("Boot steps on shutdown: ~p~n", [ToDelete]),
+ [begin
+ ets:delete(?MODULE, Step),
+ io:format("Deleted ~p~n", [Step])
+ end || {_, Step, _} <- ToDelete],
+ io:format("Run cleanup steps~n"),
+ run_cleanup_steps(TargetApps)
after
+ io:format("save boot table~n"),
save_boot_table()
- end
+ end,
+ [begin
+ {ok, Mods} = application:get_key(App, modules),
+ io:format("cleanup ~p...~n", [Mods]),
+ [begin
+ code:soft_purge(Mod),
+ code:delete(Mod),
+ false = code:is_loaded(Mod)
+ end || Mod <- Mods],
+ application:unload(App)
+ end || App <- TargetApps]
end.
-run_boot_steps(App) ->
- run_steps(App, rabbit_boot_step).
+run_cleanup_steps(Apps) ->
+ Completed = sets:new(),
+ lists:foldl(
+ fun({_, Name, _}=Step, Acc) ->
+ case sets:is_element(Name, Completed) of
+ true -> Acc;
+ false -> run_boot_step(Step, rabbit_cleanup_step),
+ sets:add_element(Name, Acc)
+ end
+ end,
+ Completed,
+ [Step || {App, _, _}=Step <- load_steps(rabbit_cleanup_step),
+ lists:member(App, Apps)]),
+ ok.
-run_steps(App, StepType) ->
- RootApps = [App|app_utils:direct_dependencies(App)],
- StepAttrs = rabbit_misc:all_app_module_attributes(StepType),
- BootSteps =
- sort_boot_steps(
- lists:usort(
- [{Mod, Steps} || {AppName, Mod, Steps} <- StepAttrs,
- lists:member(AppName, RootApps)])),
- [ok = run_boot_step(Step, StepType) || Step <- BootSteps],
+run_boot_steps() ->
+ Steps = load_steps(rabbit_boot_step),
+ [ok = run_boot_step(Step, rabbit_boot_step) || Step <- Steps],
ok.
+load_steps(StepType) ->
+ StepAttrs = rabbit_misc:all_app_module_attributes(StepType),
+ sort_boot_steps(
+ lists:usort(
+ [{Mod, {AppName, Steps}} || {AppName, Mod, Steps} <- StepAttrs])).
+
boot_error(Term={error, {timeout_waiting_for_tables, _}}, _Stacktrace) ->
AllNodes = rabbit_mnesia:cluster_nodes(all),
{Err, Nodes} =
@@ -139,6 +208,34 @@ boot_error(Reason, Fmt, Args, Stacktrace) ->
%%---------------------------------------------------------------------------
%% Private API
+force_reload(Apps) ->
+ ok = app_utils:load_applications(Apps),
+ ok = do_reload(Apps).
+
+do_reload([App|Apps]) ->
+ case application:get_key(App, modules) of
+ {ok, Modules} -> reload_all(Modules);
+ _ -> ok
+ end,
+ force_reload(Apps);
+do_reload([]) ->
+ ok.
+
+reload_all(Modules) ->
+ [begin
+ case code:soft_purge(Mod) of
+ true -> load_mod(Mod);
+ false -> io:format("unable to purge ~p~n", [Mod])
+ end
+ end || Mod <- Modules].
+
+load_mod(Mod) ->
+ case code:is_loaded(Mod) of
+ {file, preloaded} -> code:load_file(Mod);
+ {file, Path} -> code:load_abs(Path);
+ false -> code:load_file(Mod)
+ end.
+
await_startup(Apps) ->
app_utils:wait_for_applications(Apps).
@@ -160,15 +257,18 @@ ensure_boot_table() ->
end.
clean_table() ->
- ets:new(?MODULE, [named_table, public, ordered_set]).
+ delete_boot_table(),
+ case ets:info(?MODULE) of
+ undefined ->
+ ets:new(?MODULE, [named_table, public, ordered_set]);
+ _ ->
+ ok
+ end.
load_table() ->
case ets:file2tab(boot_file(), [{verify, true}]) of
- {error, _} -> error(fuck);
- {ok, _Tab} -> ok,
- io:format("Starting with pre-loaded boot table:~n~p~n"
- "----------------~n",
- [ets:tab2list(?MODULE)])
+ {error, _} -> clean_table();
+ {ok, _Tab} -> ok
end.
save_boot_table() ->
@@ -190,14 +290,17 @@ handle_app_error(Term) ->
throw({Term, App, Reason})
end.
-run_boot_step({StepName, Attributes}, StepType) ->
- case catch {StepType, already_run(StepName)} of
- {rabbit_clean_step, _} -> run_it(StepName, Attributes);
- {_, false} -> run_it(StepName, Attributes);
- {_, true} -> ok
- end.
+run_boot_step({_, _, Attributes}, rabbit_cleanup_step) ->
+ run_step(Attributes);
+run_boot_step({_, StepName, Attributes}, rabbit_boot_step) ->
+ case catch already_run(StepName) of
+ false -> ok = run_step(Attributes),
+ mark_complete(StepName);
+ true -> ok
+ end,
+ ok.
-run_it(StepName, Attributes) ->
+run_step(Attributes) ->
case [MFA || {mfa, MFA} <- Attributes] of
[] ->
ok;
@@ -205,7 +308,7 @@ run_it(StepName, Attributes) ->
[try
apply(M,F,A)
of
- ok -> mark_complete(StepName);
+ ok -> ok;
{error, Reason} -> boot_error(Reason, not_available)
catch
_:Reason -> boot_error(Reason, erlang:get_stacktrace())
@@ -239,10 +342,10 @@ log_location(Type) ->
_ -> undefined
end.
-vertices(_Module, Steps) ->
- [{StepName, {StepName, Atts}} || {StepName, Atts} <- Steps].
+vertices(_Module, {AppName, Steps}) ->
+ [{StepName, {AppName, StepName, Atts}} || {StepName, Atts} <- Steps].
-edges(_Module, Steps) ->
+edges(_Module, {_AppName, Steps}) ->
[case Key of
requires -> {StepName, OtherStep};
enables -> {OtherStep, StepName}
@@ -265,7 +368,7 @@ sort_boot_steps(UnsortedSteps) ->
digraph:delete(G),
%% Check that all mentioned {M,F,A} triples are exported.
case [{StepName, {M,F,A}} ||
- {StepName, Attributes} <- SortedSteps,
+ {_App, StepName, Attributes} <- SortedSteps,
{mfa, {M,F,A}} <- Attributes,
not erlang:function_exported(M, F, length(A))] of
[] -> SortedSteps;