PageRenderTime 19ms CodeModel.GetById 6ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/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
 21-module(ht_pool).
 22
 23-behaviour(gen_server).
 24
 25%% api
 26-export([start_link/1]).
 27-export([add_worker/2]).
 28-export([checkout_worker/1]).
 29-export([checkout_worker/2]).
 30-export([checkin_worker/2]).
 31
 32%% gen_server callbacks
 33-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
 34    code_change/3]).
 35
 36-record(state, {poolname=undefined, unused=queue:new(), checkouts=queue:new()}).
 37
 38
 39%% ----------------------------------------------------------------------------
 40%% api
 41%% ----------------------------------------------------------------------------
 42
 43%% @doc Start a linked pool manager.
 44-spec start_link(atom()) -> {ok, pid()}.
 45start_link(PoolName) ->
 46    gen_server:start_link({local, PoolName}, ?MODULE, [PoolName], []).
 47
 48%% @doc Called by ht_worker after the worker process has started.
 49-spec add_worker(atom(), pid()) -> term().
 50add_worker(PoolName, Pid) ->
 51    gen_server:cast(PoolName, {add_worker, Pid}).
 52
 53%% @doc Checkin a worker.
 54-spec checkin_worker(atom(), pid()) -> term().
 55checkin_worker(PoolName, Pid) when is_pid(Pid) ->
 56    %% only checkin live workers
 57    case is_process_alive(Pid) of
 58        true ->
 59            gen_server:cast(PoolName, {checkin_worker, Pid});
 60        false ->
 61            ok
 62    end.
 63
 64%% @doc Checkout a worker.
 65-spec checkout_worker(atom()) -> pid() | undefined.
 66checkout_worker(PoolName) ->
 67    checkout_worker(PoolName, infinity).
 68
 69%% @doc Checkout a worker with a timeout
 70-spec checkout_worker(atom(), timeout()) -> pid() | undefined.
 71checkout_worker(PoolName, Timeout) ->
 72    Worker = gen_server:call(PoolName, {checkout_worker}, Timeout),
 73    %% only checkout live workers 
 74    case is_process_alive(Worker) of
 75        true ->
 76            Worker;
 77        false ->
 78            checkout_worker(PoolName, Timeout)
 79   end.
 80
 81%% ------------------------------------------------------------------
 82%% gen_server callbacks
 83%% ------------------------------------------------------------------
 84
 85%% @private
 86init([PoolName]) ->
 87    {ok, #state{poolname=PoolName}}.
 88
 89%% @private
 90handle_call({checkout_worker}, From, State) ->
 91    case queue:out(State#state.unused) of
 92        {{value, Worker}, Unused} ->
 93            {reply, Worker, State#state{unused=Unused}};
 94        {empty, _Unused} ->
 95            Checkouts = queue:in(From, State#state.checkouts),
 96            {noreply, State#state{checkouts=Checkouts}}
 97    end.
 98
 99%% @private
100handle_cast({checkin_worker, Worker}, State) ->
101    case queue:out(State#state.checkouts) of
102        {{value, P}, Checkouts} ->
103            gen_server:reply(P, Worker),
104            {noreply, State#state{checkouts=Checkouts}};
105        {empty, _Checkouts} ->
106            Unused = queue:in(Worker, State#state.unused),
107            {noreply, State#state{unused=Unused}}
108    end;
109handle_cast({add_worker, Worker}, State) ->
110    erlang:monitor(process, Worker),
111    case queue:out(State#state.checkouts) of
112        {{value, P}, Checkouts} ->
113            gen_server:reply(P, Worker),
114            {noreply, State#state{checkouts=Checkouts}};
115        {empty, _Checkouts} ->
116            Unused = queue:in(Worker, State#state.unused),
117            {noreply, State#state{unused=Unused}}
118    end.
119
120%% @private
121handle_info({'DOWN', _, _, Worker, _}, State) ->
122    Unused = queue:from_list(lists:delete(Worker,
123        queue:to_list(State#state.unused))),
124    {noreply, State#state{unused=Unused}}.
125
126%% @private
127terminate(_Reason, _State) ->
128    ok.
129
130%% @private
131code_change(_OldVsn, State, _Extra) ->
132    {ok, State}.