cowboy_children.erl 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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([which_children/2]).
  22. -export([count_children/1]).
  23. -record(child, {
  24. pid :: pid(),
  25. streamid :: cowboy_stream:streamid() | undefined,
  26. shutdown :: timeout(),
  27. timer = undefined :: undefined | reference()
  28. }).
  29. -type children() :: [#child{}].
  30. -export_type([children/0]).
  31. -spec init() -> [].
  32. init() ->
  33. [].
  34. -spec up(Children, pid(), cowboy_stream:streamid(), timeout())
  35. -> Children when Children::children().
  36. up(Children, Pid, StreamID, Shutdown) ->
  37. [#child{
  38. pid=Pid,
  39. streamid=StreamID,
  40. shutdown=Shutdown
  41. }|Children].
  42. -spec down(Children, pid())
  43. -> {ok, cowboy_stream:streamid() | undefined, Children} | error
  44. when Children::children().
  45. down(Children0, Pid) ->
  46. case lists:keytake(Pid, #child.pid, Children0) of
  47. {value, #child{streamid=StreamID, timer=Ref}, Children} ->
  48. _ = case Ref of
  49. undefined -> ok;
  50. _ -> erlang:cancel_timer(Ref, [{async, true}, {info, false}])
  51. end,
  52. {ok, StreamID, Children};
  53. false ->
  54. error
  55. end.
  56. %% We ask the processes to shutdown first. This gives
  57. %% a chance to processes that are trapping exits to
  58. %% shut down gracefully. Others will exit immediately.
  59. %%
  60. %% @todo We currently fire one timer per process being
  61. %% shut down. This is probably not the most efficient.
  62. %% A more efficient solution could be to maintain a
  63. %% single timer and decrease the shutdown time of all
  64. %% processes when it fires. This is however much more
  65. %% complex, and there aren't that many processes that
  66. %% will need to be shutdown through this function, so
  67. %% this is left for later.
  68. -spec shutdown(Children, cowboy_stream:streamid())
  69. -> Children when Children::children().
  70. shutdown(Children0, StreamID) ->
  71. [
  72. case Child of
  73. #child{pid=Pid, streamid=StreamID, shutdown=Shutdown} ->
  74. exit(Pid, shutdown),
  75. Ref = erlang:start_timer(Shutdown, self(), {shutdown, Pid}),
  76. Child#child{streamid=undefined, timer=Ref};
  77. _ ->
  78. Child
  79. end
  80. || Child <- Children0].
  81. -spec shutdown_timeout(children(), reference(), pid()) -> ok.
  82. shutdown_timeout(Children, Ref, Pid) ->
  83. case lists:keyfind(Pid, #child.pid, Children) of
  84. #child{timer=Ref} ->
  85. exit(Pid, kill),
  86. ok;
  87. _ ->
  88. ok
  89. end.
  90. -spec terminate(children()) -> ok.
  91. terminate(Children) ->
  92. %% For each child, either ask for it to shut down,
  93. %% or cancel its shutdown timer if it already is.
  94. %%
  95. %% We do not need to flush stray timeout messages out because
  96. %% we are either terminating or switching protocols,
  97. %% and in the latter case we flush all messages.
  98. _ = [case TRef of
  99. undefined -> exit(Pid, shutdown);
  100. _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
  101. end || #child{pid=Pid, timer=TRef} <- Children],
  102. before_terminate_loop(Children).
  103. before_terminate_loop([]) ->
  104. ok;
  105. before_terminate_loop(Children) ->
  106. %% Find the longest shutdown time.
  107. Time = longest_shutdown_time(Children, 0),
  108. %% We delay the creation of the timer if one of the
  109. %% processes has an infinity shutdown value.
  110. TRef = case Time of
  111. infinity -> undefined;
  112. _ -> erlang:start_timer(Time, self(), terminate)
  113. end,
  114. %% Loop until that time or until all children are dead.
  115. terminate_loop(Children, TRef).
  116. terminate_loop([], TRef) ->
  117. %% Don't forget to cancel the timer, if any!
  118. case TRef of
  119. undefined ->
  120. ok;
  121. _ ->
  122. _ = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
  123. ok
  124. end;
  125. terminate_loop(Children, TRef) ->
  126. receive
  127. {'EXIT', Pid, _} when TRef =:= undefined ->
  128. {value, #child{shutdown=Shutdown}, Children1}
  129. = lists:keytake(Pid, #child.pid, Children),
  130. %% We delayed the creation of the timer. If a process with
  131. %% infinity shutdown just ended, we might have to start that timer.
  132. case Shutdown of
  133. infinity -> before_terminate_loop(Children1);
  134. _ -> terminate_loop(Children1, TRef)
  135. end;
  136. {'EXIT', Pid, _} ->
  137. terminate_loop(lists:keydelete(Pid, #child.pid, Children), TRef);
  138. {timeout, TRef, terminate} ->
  139. %% Brutally kill any remaining children.
  140. _ = [exit(Pid, kill) || #child{pid=Pid} <- Children],
  141. ok
  142. end.
  143. longest_shutdown_time([], Time) ->
  144. Time;
  145. longest_shutdown_time([#child{shutdown=ChildTime}|Tail], Time) when ChildTime > Time ->
  146. longest_shutdown_time(Tail, ChildTime);
  147. longest_shutdown_time([_|Tail], Time) ->
  148. longest_shutdown_time(Tail, Time).
  149. -spec which_children(children(), module()) -> [{module(), pid(), worker, [module()]}].
  150. which_children(Children, Module) ->
  151. [{Module, Pid, worker, [Module]} || #child{pid=Pid} <- Children].
  152. -spec count_children(children()) -> [{atom(), non_neg_integer()}].
  153. count_children(Children) ->
  154. Count = length(Children),
  155. [
  156. {specs, 1},
  157. {active, Count},
  158. {supervisors, 0},
  159. {workers, Count}
  160. ].