Current section
Files
Jump to
Current section
Files
src/pipe_supervisor.erl
%%
%% Copyright (c) 2012 - 2013, Dmitry Kolesnikov
%% Copyright (c) 2012 - 2013, Mario Cardona
%% All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @doc
%% reusable pipeline supervisor
-module(pipe_supervisor).
-behaviour(gen_server).
-export([
start_link/3
, init/1
, terminate/2
, handle_call/3
, handle_cast/2
, handle_info/2
, code_change/3
]).
-record(state, {
lifecycle = undefined :: transient | temporary | permanent
, strategy = undefined :: one_for_all
, intensity = undefined :: non_neg_integer()
, interval = undefined :: non_neg_integer()
, spec = undefined :: [_]
, opts = undefined :: [_]
, stages = undefined :: [_]
, failure = []
}).
%%%----------------------------------------------------------------------------
%%%
%%% Factory
%%%
%%%----------------------------------------------------------------------------
start_link(Mod, Args, Opts) ->
gen_server:start_link(?MODULE, [Mod, Args, Opts], []).
%%
%%
init([Mod, Args, Opts]) ->
process_flag(trap_exit, true),
{ok, {Supervisor, Spec}} = Mod:init(Args),
{ok,
init_pipe(#state{
lifecycle = lifecycle(spec(Supervisor)),
strategy = strategy(spec(Supervisor)),
intensity = intensity(spec(Supervisor)),
interval = interval(spec(Supervisor)),
spec = Spec,
opts = Opts
})
}.
spec({_, _, _, _} = Spec) ->
Spec;
spec({Strategy, Intensity, Interval}) ->
{permanent, Strategy, Intensity, Interval}.
lifecycle({permanent, _, _, _}) ->
permanent;
lifecycle({transient, _, _, _}) ->
transient;
lifecycle({temporary, _, _, _}) ->
temporary.
strategy({_, one_for_one, _, _}) ->
one_for_all;
strategy({_, one_for_all, _, _}) ->
one_for_all;
strategy({_, rest_for_all, _, _}) ->
one_for_all.
intensity({_, _, Intensity, _})
when Intensity >= 0 ->
Intensity.
interval({_, _, _, Interval})
when Interval >= 0 ->
Interval.
%%
%%
terminate(_, #state{} = State) ->
free_pipe(State),
ok.
%%
%%
handle_info({'EXIT', Pid, normal}, #state{lifecycle = temporary} = State) ->
heir(normal, Pid, State),
{stop, normal, State};
handle_info({'EXIT', Pid, Reason}, #state{lifecycle = temporary} = State) ->
heir(Reason, Pid, State),
{stop, shutdown, State};
handle_info({'EXIT', Pid, normal}, #state{lifecycle = transient} = State) ->
heir(normal, Pid, State),
{stop, normal, State};
handle_info({'EXIT', Pid, Reason}, #state{lifecycle = transient} = State) ->
recover_pipe(Reason, Pid, State);
handle_info({'EXIT', Pid, Reason}, #state{lifecycle = permanent} = State) ->
recover_pipe(Reason, Pid, State);
handle_info(_, State) ->
{noreply, State}.
%%
%%
handle_cast(_, State) ->
{noreply, State}.
%%
%%
handle_call(which_children, _, #state{stages = Stages} = State) ->
{reply,
[{Id, Pid, worker,dynamic} || {Id, Pid} <- Stages],
State
}.
%%
%%
code_change(_Vsn, State, _) ->
{ok, State}.
%%
%%
heir(Reason, Pid, #state{opts = Opts}) ->
case proplists:get_value(heir, Opts) of
undefined ->
ok;
Heir ->
Heir ! {'DOWN', self(), pipe, Pid, Reason}
end.
%%
%%
recover_pipe(Reason, Pid, #state{} = State0) ->
case is_permanent_failure(State0) of
{false, State1} ->
{noreply, respawn(State1)};
{true, State1} ->
heir(Reason, Pid, State1),
{stop, shutdown, State1}
end.
is_permanent_failure(#state{failure = Failure0, interval = Interval, intensity = Intensity} = State) ->
T0 = erlang:monotonic_time(seconds),
T1 = T0 - Interval,
Failure1 = lists:filter(fun(X) -> X >= T1 end, [T0 | Failure0]),
{length(Failure1) > Intensity, State#state{failure = Failure1}}.
%%
%%
init_pipe(#state{spec = Spec, opts = Opts} = State) ->
Pids = pipe:make([init_pipe_stage(MFA) || MFA <- Spec], Opts),
State#state{
stages = lists:zip(lists:seq(1, length(Pids)), Pids)
}.
init_pipe_stage({M, F, A}) ->
{ok, Pid} = (catch erlang:apply(M, F, A)),
Pid.
%%
%%
free_pipe(#state{stages = Stages} = State) ->
lists:foreach(fun free_pipe_stage/1, Stages),
State#state{
stages = undefined
}.
free_pipe_stage({_, Pid}) ->
%% See Erlang/OTP supervisor for details
erlang:monitor(process, Pid),
erlang:unlink(Pid),
receive
{'EXIT', Pid, Reason} ->
receive
{'DOWN', _, process, Pid, _} ->
{error, Reason}
end
after 0 ->
exit(Pid, shutdown),
receive
{'DOWN', _MRef, process, Pid, shutdown} ->
ok;
{'DOWN', _MRef, process, Pid, Reason} ->
{error, Reason}
% @todo: shutdown timeout
% after Time ->
% exit(Pid, kill),
% receive
% {'DOWN', _MRef, process, Pid, Reason} ->
% {error, Reason}
% end
end
end.
%%
%%
respawn(State) ->
init_pipe(free_pipe(State)).