Skip to content

Instantly share code, notes, and snippets.

@RJ
Created November 8, 2012 22:38
Show Gist options
  • Save RJ/4042310 to your computer and use it in GitHub Desktop.
Save RJ/4042310 to your computer and use it in GitHub Desktop.
RabbitMQ boot_step system
%% Boot system extracted from rabbitmq-server source code
%% The RabbitMQ license banner follows:
%%
%% The contents of this file are subject to the Mozilla Public License
%% Version 1.1 (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.mozilla.org/MPL/
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and
%% limitations under the License.
%%
%% The Original Code is RabbitMQ.
%%
%% The Initial Developer of the Original Code is VMware, Inc.
%% Copyright (c) 2007-2012 VMware, Inc. All rights reserved.
%%
%% Extracted and slightly modified by Richard Jones <rj@metabrew.com>
%%
%% HOW TO USE
%%
%% 1) Read this: https://github.com/videlalvaro/rabbit-internals/blob/master/rabbit_boot_process.md
%% Thanks to Alvaro Videla (@old_sound) for documenting this
%%
%% 2) Note that I added a field, and changed the module attribute name:
%%
%% -boot_step({my_app, setup_mnesia_tables,
%% [{description, "Initializing mnesia tables"},
%% {mfa, {my_mnesia_module, setup, []}},
%% {requires, pre_boot},
%% {enables, database}]).
%%
%% Now you can have multiple applications in one release, but only run the boot
%% steps for a named group (my_app). Important if you use OTP releases.
%%
%% Then when your app starts, run rabbit_boot_system:run(my_app).
%%
-module(rabbit_boot_system).
-export([run/1]).
%%-compile(export_all).
run(BootName) ->
lists:foreach(fun run_boot_step/1, boot_steps(BootName)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
boot_steps(BootName) ->
boot_steps(BootName, boot_step).
boot_steps(BootName, AttributeName) ->
sort_boot_steps(
filter_boot_steps(BootName,
all_module_attributes(AttributeName))).
filter_boot_steps(BootName, ModSteps) ->
lists:map(fun({ModuleName, StepsList}) ->
{ModuleName, [{N,Args} || {BN,N,Args} <- StepsList, BN =:= BootName]}
end, ModSteps).
run_boot_step({StepName, Attributes}) ->
Description = case lists:keysearch(description, 1, Attributes) of
{value, {_, D}} -> D;
false -> StepName
end,
case [MFA || {mfa, MFA} <- Attributes] of
[] ->
io:format("# ~s~n", [Description]);
MFAs ->
io:format("## ~s~n", [Description]),
[try
apply(M,F,A)
catch
_:Reason -> boot_error(Reason, erlang:get_stacktrace())
end || {M,F,A} <- MFAs],
ok
end.
vertices(_Module, Steps) ->
[{StepName, {StepName, Atts}} || {StepName, Atts} <- Steps].
edges(_Module, Steps) ->
[case Key of
requires -> {StepName, OtherStep};
enables -> {OtherStep, StepName}
end || {StepName, Atts} <- Steps,
{Key, OtherStep} <- Atts,
Key =:= requires orelse Key =:= enables].
sort_boot_steps(UnsortedSteps) ->
case build_acyclic_graph(fun vertices/2, fun edges/2,
UnsortedSteps) of
{ok, G} ->
%% Use topological sort to find a consistent ordering (if
%% there is one, otherwise fail).
SortedSteps = lists:reverse(
[begin
{StepName, Step} = digraph:vertex(G,
StepName),
Step
end || StepName <- digraph_utils:topsort(G)]),
digraph:delete(G),
%% Check that all mentioned {M,F,A} triples are exported.
case [{StepName, {M,F,A}} ||
{StepName, Attributes} <- SortedSteps,
{mfa, {M,F,A}} <- Attributes,
not erlang:function_exported(M, F, length(A))] of
[] -> SortedSteps;
MissingFunctions -> basic_boot_error(
{missing_functions, MissingFunctions},
"Boot step functions not exported: ~p~n",
[MissingFunctions])
end;
{error, {vertex, duplicate, StepName}} ->
basic_boot_error({duplicate_boot_step, StepName},
"Duplicate boot step name: ~w~n", [StepName]);
{error, {edge, Reason, From, To}} ->
basic_boot_error(
{invalid_boot_step_dependency, From, To},
"Could not add boot step dependency of ~w on ~w:~n~s",
[To, From,
case Reason of
{bad_vertex, V} ->
io_lib:format("Boot step not registered: ~w~n", [V]);
{bad_edge, [First | Rest]} ->
[io_lib:format("Cyclic dependency: ~w", [First]),
[io_lib:format(" depends on ~w", [Next]) ||
Next <- Rest],
io_lib:format(" depends on ~w~n", [First])]
end])
end.
boot_error(Reason, Stacktrace) ->
Fmt = "Error description:~n ~p~n~n" ++
"Log files (may contain more information):~n ~s~n ~s~n~n",
Args = [Reason, log_location(kernel), log_location(sasl)],
boot_error(Reason, Fmt, Args, Stacktrace).
boot_error(Reason, Fmt, Args, Stacktrace) ->
case Stacktrace of
not_available -> basic_boot_error(Reason, Fmt, Args);
_ -> basic_boot_error(Reason, Fmt ++
"Stack trace:~n ~p~n~n",
Args ++ [Stacktrace])
end.
basic_boot_error(Reason, Format, Args) ->
io:format("~n~nBOOT FAILED~n===========~n~n" ++ Format, Args),
local_info_msg(Format, Args),
timer:sleep(1000),
exit({?MODULE, failure_during_boot, Reason}).
all_module_attributes(Name) ->
Modules =
lists:usort(
lists:append(
[Modules || {App, _, _} <- application:loaded_applications(),
{ok, Modules} <- [application:get_key(App, modules)]])),
lists:foldl(
fun (Module, Acc) ->
case lists:append([Atts || {N, Atts} <- module_attributes(Module),
N =:= Name]) of
[] -> Acc;
Atts -> [{Module, Atts} | Acc]
end
end, [], Modules).
build_acyclic_graph(VertexFun, EdgeFun, Graph) ->
G = digraph:new([acyclic]),
try
[case digraph:vertex(G, Vertex) of
false -> digraph:add_vertex(G, Vertex, Label);
_ -> ok = throw({graph_error, {vertex, duplicate, Vertex}})
end || {Module, Atts} <- Graph,
{Vertex, Label} <- VertexFun(Module, Atts)],
[case digraph:add_edge(G, From, To) of
{error, E} -> throw({graph_error, {edge, E, From, To}});
_ -> ok
end || {Module, Atts} <- Graph,
{From, To} <- EdgeFun(Module, Atts)],
{ok, G}
catch {graph_error, Reason} ->
true = digraph:delete(G),
{error, Reason}
end.
module_attributes(Module) ->
case catch Module:module_info(attributes) of
{'EXIT', {undef, [{Module, module_info, _} | _]}} ->
io:format("WARNING: module ~p not found, so not scanned for boot steps.~n",
[Module]),
[];
{'EXIT', Reason} ->
exit(Reason);
V ->
V
end.
log_location(Type) ->
case application:get_env(rabbit, case Type of
kernel -> error_logger;
sasl -> sasl_error_logger
end) of
{ok, {file, File}} -> File;
{ok, false} -> undefined;
{ok, tty} -> tty;
{ok, silent} -> undefined;
{ok, Bad} -> throw({error, {cannot_log_to_file, Bad}});
_ -> undefined
end.
%% Execute Fun using the IO system of the local node (i.e. the node on
%% which the code is executing).
with_local_io(Fun) ->
GL = group_leader(),
group_leader(whereis(user), self()),
try
Fun()
after
group_leader(GL, self())
end.
%% Log an info message on the local node using the standard logger.
%% Use this if rabbit isn't running and the call didn't originate on
%% the local node (e.g. rabbitmqctl calls).
local_info_msg(Format, Args) ->
with_local_io(fun () -> error_logger:info_msg(Format, Args) end).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment