/src/ht_pool.erl

http://github.com/bfrog/hottub · Erlang · 132 lines · 73 code · 19 blank · 40 comment · 0 complexity · 1e757d47b6df78f1b6a6e5cc753390b1 MD5 · raw file

  1. %% @author Tom Burdick <thomas.burdick@gmail.com>
  2. %% @copyright 2011 Tom Burdick
  3. %% @doc Hot Tub Pool Manager.
  4. %% Keeps two simple queues. One of workers and another of pending checkouts
  5. %% if there are no available workers. On checkin or addition of a worker
  6. %% if pending checkouts are waiting the worker is immediately handed off
  7. %% with little cost it is otherwise added back in to the queue of unused
  8. %% workers.
  9. %%
  10. %% A best attempt is made to ensure a worker is alive when checked out though
  11. %% in some circumstances a process may be dead on arrival if the last request
  12. %% caused it to have a delayed termination. Such scenarios are easily
  13. %% possibly when gen_server:cast or something equivalent.
  14. %%
  15. %% checkin/checkout should not be used directly. Instead the simple,
  16. %% functional wrapper hottub:execute should be used or one of its
  17. %% additional halpers hottub:call or hottub:cast.
  18. %%
  19. %% @end
  20. -module(ht_pool).
  21. -behaviour(gen_server).
  22. %% api
  23. -export([start_link/1]).
  24. -export([add_worker/2]).
  25. -export([checkout_worker/1]).
  26. -export([checkout_worker/2]).
  27. -export([checkin_worker/2]).
  28. %% gen_server callbacks
  29. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
  30. code_change/3]).
  31. -record(state, {poolname=undefined, unused=queue:new(), checkouts=queue:new()}).
  32. %% ----------------------------------------------------------------------------
  33. %% api
  34. %% ----------------------------------------------------------------------------
  35. %% @doc Start a linked pool manager.
  36. -spec start_link(atom()) -> {ok, pid()}.
  37. start_link(PoolName) ->
  38. gen_server:start_link({local, PoolName}, ?MODULE, [PoolName], []).
  39. %% @doc Called by ht_worker after the worker process has started.
  40. -spec add_worker(atom(), pid()) -> term().
  41. add_worker(PoolName, Pid) ->
  42. gen_server:cast(PoolName, {add_worker, Pid}).
  43. %% @doc Checkin a worker.
  44. -spec checkin_worker(atom(), pid()) -> term().
  45. checkin_worker(PoolName, Pid) when is_pid(Pid) ->
  46. %% only checkin live workers
  47. case is_process_alive(Pid) of
  48. true ->
  49. gen_server:cast(PoolName, {checkin_worker, Pid});
  50. false ->
  51. ok
  52. end.
  53. %% @doc Checkout a worker.
  54. -spec checkout_worker(atom()) -> pid() | undefined.
  55. checkout_worker(PoolName) ->
  56. checkout_worker(PoolName, infinity).
  57. %% @doc Checkout a worker with a timeout
  58. -spec checkout_worker(atom(), timeout()) -> pid() | undefined.
  59. checkout_worker(PoolName, Timeout) ->
  60. Worker = gen_server:call(PoolName, {checkout_worker}, Timeout),
  61. %% only checkout live workers
  62. case is_process_alive(Worker) of
  63. true ->
  64. Worker;
  65. false ->
  66. checkout_worker(PoolName, Timeout)
  67. end.
  68. %% ------------------------------------------------------------------
  69. %% gen_server callbacks
  70. %% ------------------------------------------------------------------
  71. %% @private
  72. init([PoolName]) ->
  73. {ok, #state{poolname=PoolName}}.
  74. %% @private
  75. handle_call({checkout_worker}, From, State) ->
  76. case queue:out(State#state.unused) of
  77. {{value, Worker}, Unused} ->
  78. {reply, Worker, State#state{unused=Unused}};
  79. {empty, _Unused} ->
  80. Checkouts = queue:in(From, State#state.checkouts),
  81. {noreply, State#state{checkouts=Checkouts}}
  82. end.
  83. %% @private
  84. handle_cast({checkin_worker, Worker}, State) ->
  85. case queue:out(State#state.checkouts) of
  86. {{value, P}, Checkouts} ->
  87. gen_server:reply(P, Worker),
  88. {noreply, State#state{checkouts=Checkouts}};
  89. {empty, _Checkouts} ->
  90. Unused = queue:in(Worker, State#state.unused),
  91. {noreply, State#state{unused=Unused}}
  92. end;
  93. handle_cast({add_worker, Worker}, State) ->
  94. erlang:monitor(process, Worker),
  95. case queue:out(State#state.checkouts) of
  96. {{value, P}, Checkouts} ->
  97. gen_server:reply(P, Worker),
  98. {noreply, State#state{checkouts=Checkouts}};
  99. {empty, _Checkouts} ->
  100. Unused = queue:in(Worker, State#state.unused),
  101. {noreply, State#state{unused=Unused}}
  102. end.
  103. %% @private
  104. handle_info({'DOWN', _, _, Worker, _}, State) ->
  105. Unused = queue:from_list(lists:delete(Worker,
  106. queue:to_list(State#state.unused))),
  107. {noreply, State#state{unused=Unused}}.
  108. %% @private
  109. terminate(_Reason, _State) ->
  110. ok.
  111. %% @private
  112. code_change(_OldVsn, State, _Extra) ->
  113. {ok, State}.