Created
November 8, 2012 22:38
-
-
Save RJ/4042310 to your computer and use it in GitHub Desktop.
RabbitMQ boot_step system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% 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