/src/ht_pool.erl
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}.