Packages
Small, fast, modern HTTP server.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/cowboy_children.erl
%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>%%%% Permission to use, copy, modify, and/or distribute this software for any%% purpose with or without fee is hereby granted, provided that the above%% copyright notice and this permission notice appear in all copies.%%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.-module(cowboy_children).-export([init/0]).-export([up/4]).-export([down/2]).-export([shutdown/2]).-export([shutdown_timeout/3]).-export([terminate/1]).-export([handle_supervisor_call/4]).-record(child, { pid :: pid(), streamid :: cowboy_stream:streamid() | undefined, shutdown :: timeout(), timer = undefined :: undefined | reference()}).-type children() :: [#child{}].-export_type([children/0]).-spec init() -> [].init() -> [].-spec up(Children, pid(), cowboy_stream:streamid(), timeout()) -> Children when Children::children().up(Children, Pid, StreamID, Shutdown) -> [#child{ pid=Pid, streamid=StreamID, shutdown=Shutdown }|Children].-spec down(Children, pid()) -> {ok, cowboy_stream:streamid() | undefined, Children} | error when Children::children().down(Children0, Pid) -> case lists:keytake(Pid, #child.pid, Children0) of {value, #child{streamid=StreamID, timer=Ref}, Children} -> _ = case Ref of undefined -> ok; _ -> erlang:cancel_timer(Ref, [{async, true}, {info, false}]) end, {ok, StreamID, Children}; false -> error end.%% We ask the processes to shutdown first. This gives%% a chance to processes that are trapping exits to%% shut down gracefully. Others will exit immediately.%%%% @todo We currently fire one timer per process being%% shut down. This is probably not the most efficient.%% A more efficient solution could be to maintain a%% single timer and decrease the shutdown time of all%% processes when it fires. This is however much more%% complex, and there aren't that many processes that%% will need to be shutdown through this function, so%% this is left for later.-spec shutdown(Children, cowboy_stream:streamid()) -> Children when Children::children().shutdown(Children0, StreamID) -> [ case Child of #child{pid=Pid, streamid=StreamID, shutdown=Shutdown} -> exit(Pid, shutdown), Ref = erlang:start_timer(Shutdown, self(), {shutdown, Pid}), Child#child{streamid=undefined, timer=Ref}; _ -> Child end || Child <- Children0].-spec shutdown_timeout(children(), reference(), pid()) -> ok.shutdown_timeout(Children, Ref, Pid) -> case lists:keyfind(Pid, #child.pid, Children) of #child{timer=Ref} -> exit(Pid, kill), ok; _ -> ok end.-spec terminate(children()) -> ok.terminate(Children) -> %% For each child, either ask for it to shut down, %% or cancel its shutdown timer if it already is. %% %% We do not need to flush stray timeout messages out because %% we are either terminating or switching protocols, %% and in the latter case we flush all messages. _ = [case TRef of undefined -> exit(Pid, shutdown); _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}]) end || #child{pid=Pid, timer=TRef} <- Children], before_terminate_loop(Children).before_terminate_loop([]) -> ok;before_terminate_loop(Children) -> %% Find the longest shutdown time. Time = longest_shutdown_time(Children, 0), %% We delay the creation of the timer if one of the %% processes has an infinity shutdown value. TRef = case Time of infinity -> undefined; _ -> erlang:start_timer(Time, self(), terminate) end, %% Loop until that time or until all children are dead. terminate_loop(Children, TRef).terminate_loop([], TRef) -> %% Don't forget to cancel the timer, if any! case TRef of undefined -> ok; _ -> _ = erlang:cancel_timer(TRef, [{async, true}, {info, false}]), ok end;terminate_loop(Children, TRef) -> receive {'EXIT', Pid, _} when TRef =:= undefined -> {value, #child{shutdown=Shutdown}, Children1} = lists:keytake(Pid, #child.pid, Children), %% We delayed the creation of the timer. If a process with %% infinity shutdown just ended, we might have to start that timer. case Shutdown of infinity -> before_terminate_loop(Children1); _ -> terminate_loop(Children1, TRef) end; {'EXIT', Pid, _} -> terminate_loop(lists:keydelete(Pid, #child.pid, Children), TRef); {timeout, TRef, terminate} -> %% Brutally kill any remaining children. _ = [exit(Pid, kill) || #child{pid=Pid} <- Children], ok end.longest_shutdown_time([], Time) -> Time;longest_shutdown_time([#child{shutdown=ChildTime}|Tail], Time) when ChildTime > Time -> longest_shutdown_time(Tail, ChildTime);longest_shutdown_time([_|Tail], Time) -> longest_shutdown_time(Tail, Time).-spec handle_supervisor_call(any(), {pid(), any()}, children(), module()) -> ok.handle_supervisor_call(which_children, {From, Tag}, Children, Module) -> From ! {Tag, which_children(Children, Module)}, ok;handle_supervisor_call(count_children, {From, Tag}, Children, _) -> From ! {Tag, count_children(Children)}, ok;%% We disable start_child since only incoming requests%% end up creating a new process.handle_supervisor_call({start_child, _}, {From, Tag}, _, _) -> From ! {Tag, {error, start_child_disabled}}, ok;%% All other calls refer to children. We act in a similar way%% to a simple_one_for_one so we never find those.handle_supervisor_call(_, {From, Tag}, _, _) -> From ! {Tag, {error, not_found}}, ok.-spec which_children(children(), module()) -> [{module(), pid(), worker, [module()]}].which_children(Children, Module) -> [{Module, Pid, worker, [Module]} || #child{pid=Pid} <- Children].-spec count_children(children()) -> [{atom(), non_neg_integer()}].count_children(Children) -> Count = length(Children), [ {specs, 1}, {active, Count}, {supervisors, 0}, {workers, Count} ].