pooler_starter.erl 4.8 KB

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