pooler_starter.erl 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. %% @author Seth Falcon <seth@userprimary.net>
  2. %% @copyright 2012-2013 Seth Falcon
  3. %% @doc Helper gen_server to start pool members
  4. %%
  5. -module(pooler_starter).
  6. -behaviour(gen_server).
  7. -include("pooler.hrl").
  8. -include_lib("eunit/include/eunit.hrl").
  9. %% ------------------------------------------------------------------
  10. %% API Function Exports
  11. %% ------------------------------------------------------------------
  12. -export([start_link/2,
  13. start_member/1,
  14. start_member/2,
  15. stop_member_async/1,
  16. stop/1]).
  17. %% ------------------------------------------------------------------
  18. %% gen_server Function Exports
  19. %% ------------------------------------------------------------------
  20. -export([init/1,
  21. handle_call/3,
  22. handle_cast/2,
  23. handle_info/2,
  24. terminate/2,
  25. code_change/3]).
  26. %% To help with testing internal functions
  27. -ifdef(TEST).
  28. -compile([export_all]).
  29. -endif.
  30. %% ------------------------------------------------------------------
  31. %% API Function Definitions
  32. %% ------------------------------------------------------------------
  33. start_link(Pool, Parent) ->
  34. gen_server:start_link(?MODULE, {Pool, Parent}, []).
  35. stop(Starter) ->
  36. gen_server:cast(Starter, stop).
  37. %% @doc Start a member for the specified `Pool'.
  38. %%
  39. %% Member creation with this call is async. This function returns
  40. %% immediately with create process' pid. When the member has been
  41. %% created it is sent to the specified pool via
  42. %% {@link pooler:accept_member/2}.
  43. %%
  44. %% Each call starts a single use `pooler_starter' instance via
  45. %% `pooler_starter_sup'. The instance terminates normally after
  46. %% creating a single member.
  47. -spec start_member(#pool{}) -> pid().
  48. start_member(Pool) ->
  49. {ok, Pid} = pooler_starter_sup:new_starter(Pool, pool),
  50. Pid.
  51. %% @doc Same as {@link start_member/1} except that instead of calling
  52. %% {@link pooler:accept_member/2} a raw message is sent to `Parent' of
  53. %% the form `{accept_member, {Ref, Member}'. Where `Member' will
  54. %% either be the member pid or an error term and `Ref' will be the
  55. %% Pid of the starter.
  56. %%
  57. %% This is used by the init function in the `pooler' to start the
  58. %% initial set of pool members in parallel.
  59. start_member(Pool, Parent) ->
  60. {ok, Pid} = pooler_starter_sup:new_starter(Pool, Parent),
  61. Pid.
  62. %% @doc Stop a member in the pool
  63. %% Member creation can take too long. In this case, the starter
  64. %% needs to be informed that even if creation succeeds, the
  65. %% started child should be not be sent back and should be
  66. %% cleaned up
  67. -spec stop_member_async(pid()) -> ok.
  68. stop_member_async(Pid) ->
  69. gen_server:cast(Pid, stop_member).
  70. %% ------------------------------------------------------------------
  71. %% gen_server Function Definitions
  72. %% ------------------------------------------------------------------
  73. -record(starter, {pool,
  74. parent,
  75. msg}).
  76. -spec init({#pool{}, pid() | atom()}) -> {'ok', #starter{}, 0}.
  77. init({Pool, Parent}) ->
  78. %% trigger immediate timeout message, which we'll use to trigger
  79. %% the member start.
  80. {ok, #starter{pool = Pool, parent = Parent}, 0}.
  81. handle_call(_Request, _From, State) ->
  82. {noreply, State}.
  83. handle_cast(stop_member, #starter{msg = {_Me, Pid}, pool = #pool{member_sup = MemberSup}} = State) ->
  84. %% The process we were starting is no longer valid for the pool.
  85. %% Cleanup the process and stop normally.
  86. supervisor:terminate_child(MemberSup, Pid),
  87. {stop, normal, State};
  88. handle_cast(accept_member, #starter{msg = Msg, parent = Parent, pool = #pool{name = PoolName}} = State) ->
  89. %% Process creation has succeeded. Send the member to the pooler
  90. %% gen_server to be accepted. Pooler gen_server will notify
  91. %% us if the member was accepted or needs to cleaned up.
  92. send_accept_member(Parent, PoolName, Msg),
  93. {noreply, State};
  94. handle_cast(stop, State) ->
  95. {stop, normal, State};
  96. handle_cast(_Request, State) ->
  97. {noreply, State}.
  98. -spec handle_info(_, _) -> {'noreply', _}.
  99. handle_info(timeout,
  100. #starter{pool = Pool} = State) ->
  101. Msg = do_start_member(Pool),
  102. accept_member_async(self()),
  103. {noreply, State#starter{msg = Msg}};
  104. handle_info(_Info, State) ->
  105. {noreply, State}.
  106. -spec terminate(_, _) -> 'ok'.
  107. terminate(_Reason, _State) ->
  108. ok.
  109. -spec code_change(_, _, _) -> {'ok', _}.
  110. code_change(_OldVsn, State, _Extra) ->
  111. {ok, State}.
  112. do_start_member(#pool{member_sup = PoolSup, name = PoolName}) ->
  113. case supervisor:start_child(PoolSup, []) of
  114. {ok, Pid} ->
  115. {self(), Pid};
  116. Error ->
  117. error_logger:error_msg("pool '~s' failed to start member: ~p",
  118. [PoolName, Error]),
  119. {self(), Error}
  120. end.
  121. send_accept_member(pool, PoolName, Msg) ->
  122. pooler:accept_member(PoolName, Msg);
  123. send_accept_member(Pid, _PoolName, Msg) ->
  124. Pid ! {accept_member, Msg},
  125. ok.
  126. accept_member_async(Pid) ->
  127. gen_server:cast(Pid, accept_member).