cowboy_children.erl 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. %% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cowboy_children).
  15. -export([init/0]).
  16. -export([up/4]).
  17. -export([down/2]).
  18. -export([shutdown/2]).
  19. -export([shutdown_timeout/3]).
  20. -export([terminate/1]).
  21. -export([handle_supervisor_call/4]).
  22. -record(child, {
  23. pid :: pid(),
  24. streamid :: cowboy_stream:streamid() | undefined,
  25. shutdown :: timeout(),
  26. timer = undefined :: undefined | reference()
  27. }).
  28. -type children() :: [#child{}].
  29. -export_type([children/0]).
  30. -spec init() -> [].
  31. init() ->
  32. [].
  33. -spec up(Children, pid(), cowboy_stream:streamid(), timeout())
  34. -> Children when Children::children().
  35. up(Children, Pid, StreamID, Shutdown) ->
  36. [#child{
  37. pid=Pid,
  38. streamid=StreamID,
  39. shutdown=Shutdown
  40. }|Children].
  41. -spec down(Children, pid())
  42. -> {ok, cowboy_stream:streamid() | undefined, Children} | error
  43. when Children::children().
  44. down(Children0, Pid) ->
  45. case lists:keytake(Pid, #child.pid, Children0) of
  46. {value, #child{streamid=StreamID, timer=Ref}, Children} ->
  47. _ = case Ref of
  48. undefined -> ok;
  49. _ -> erlang:cancel_timer(Ref, [{async, true}, {info, false}])
  50. end,
  51. {ok, StreamID, Children};
  52. false ->
  53. error
  54. end.
  55. %% We ask the processes to shutdown first. This gives
  56. %% a chance to processes that are trapping exits to
  57. %% shut down gracefully. Others will exit immediately.
  58. %%
  59. %% @todo We currently fire one timer per process being
  60. %% shut down. This is probably not the most efficient.
  61. %% A more efficient solution could be to maintain a
  62. %% single timer and decrease the shutdown time of all
  63. %% processes when it fires. This is however much more
  64. %% complex, and there aren't that many processes that
  65. %% will need to be shutdown through this function, so
  66. %% this is left for later.
  67. -spec shutdown(Children, cowboy_stream:streamid())
  68. -> Children when Children::children().
  69. shutdown(Children0, StreamID) ->
  70. [
  71. case Child of
  72. #child{pid=Pid, streamid=StreamID, shutdown=Shutdown} ->
  73. exit(Pid, shutdown),
  74. Ref = erlang:start_timer(Shutdown, self(), {shutdown, Pid}),
  75. Child#child{streamid=undefined, timer=Ref};
  76. _ ->
  77. Child
  78. end
  79. || Child <- Children0].
  80. -spec shutdown_timeout(children(), reference(), pid()) -> ok.
  81. shutdown_timeout(Children, Ref, Pid) ->
  82. case lists:keyfind(Pid, #child.pid, Children) of
  83. #child{timer=Ref} ->
  84. exit(Pid, kill),
  85. ok;
  86. _ ->
  87. ok
  88. end.
  89. -spec terminate(children()) -> ok.
  90. terminate(Children) ->
  91. %% For each child, either ask for it to shut down,
  92. %% or cancel its shutdown timer if it already is.
  93. %%
  94. %% We do not need to flush stray timeout messages out because
  95. %% we are either terminating or switching protocols,
  96. %% and in the latter case we flush all messages.
  97. _ = [case TRef of
  98. undefined -> exit(Pid, shutdown);
  99. _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
  100. end || #child{pid=Pid, timer=TRef} <- Children],
  101. before_terminate_loop(Children).
  102. before_terminate_loop([]) ->
  103. ok;
  104. before_terminate_loop(Children) ->
  105. %% Find the longest shutdown time.
  106. Time = longest_shutdown_time(Children, 0),
  107. %% We delay the creation of the timer if one of the
  108. %% processes has an infinity shutdown value.
  109. TRef = case Time of
  110. infinity -> undefined;
  111. _ -> erlang:start_timer(Time, self(), terminate)
  112. end,
  113. %% Loop until that time or until all children are dead.
  114. terminate_loop(Children, TRef).
  115. terminate_loop([], TRef) ->
  116. %% Don't forget to cancel the timer, if any!
  117. case TRef of
  118. undefined ->
  119. ok;
  120. _ ->
  121. _ = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
  122. ok
  123. end;
  124. terminate_loop(Children, TRef) ->
  125. receive
  126. {'EXIT', Pid, _} when TRef =:= undefined ->
  127. {value, #child{shutdown=Shutdown}, Children1}
  128. = lists:keytake(Pid, #child.pid, Children),
  129. %% We delayed the creation of the timer. If a process with
  130. %% infinity shutdown just ended, we might have to start that timer.
  131. case Shutdown of
  132. infinity -> before_terminate_loop(Children1);
  133. _ -> terminate_loop(Children1, TRef)
  134. end;
  135. {'EXIT', Pid, _} ->
  136. terminate_loop(lists:keydelete(Pid, #child.pid, Children), TRef);
  137. {timeout, TRef, terminate} ->
  138. %% Brutally kill any remaining children.
  139. _ = [exit(Pid, kill) || #child{pid=Pid} <- Children],
  140. ok
  141. end.
  142. longest_shutdown_time([], Time) ->
  143. Time;
  144. longest_shutdown_time([#child{shutdown=ChildTime}|Tail], Time) when ChildTime > Time ->
  145. longest_shutdown_time(Tail, ChildTime);
  146. longest_shutdown_time([_|Tail], Time) ->
  147. longest_shutdown_time(Tail, Time).
  148. -spec handle_supervisor_call(any(), {pid(), any()}, children(), module()) -> ok.
  149. handle_supervisor_call(which_children, {From, Tag}, Children, Module) ->
  150. From ! {Tag, which_children(Children, Module)},
  151. ok;
  152. handle_supervisor_call(count_children, {From, Tag}, Children, _) ->
  153. From ! {Tag, count_children(Children)},
  154. ok;
  155. %% We disable start_child since only incoming requests
  156. %% end up creating a new process.
  157. handle_supervisor_call({start_child, _}, {From, Tag}, _, _) ->
  158. From ! {Tag, {error, start_child_disabled}},
  159. ok;
  160. %% All other calls refer to children. We act in a similar way
  161. %% to a simple_one_for_one so we never find those.
  162. handle_supervisor_call(_, {From, Tag}, _, _) ->
  163. From ! {Tag, {error, not_found}},
  164. ok.
  165. -spec which_children(children(), module()) -> [{module(), pid(), worker, [module()]}].
  166. which_children(Children, Module) ->
  167. [{Module, Pid, worker, [Module]} || #child{pid=Pid} <- Children].
  168. -spec count_children(children()) -> [{atom(), non_neg_integer()}].
  169. count_children(Children) ->
  170. Count = length(Children),
  171. [
  172. {specs, 1},
  173. {active, Count},
  174. {supervisors, 0},
  175. {workers, Count}
  176. ].