pax_global_header00006660000000000000000000000064141117730650014517gustar00rootroot0000000000000052 comment=e7b146c04db4764f82ef70fbf1e6c829243fe25b erlang-poolboy-1.5.2+dfsg/000077500000000000000000000000001411177306500153745ustar00rootroot00000000000000erlang-poolboy-1.5.2+dfsg/.editorconfig000066400000000000000000000004201411177306500200450ustar00rootroot00000000000000# EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true # 4 space indentation [*.{erl,src}] indent_style = space indent_size = 4 erlang-poolboy-1.5.2+dfsg/.gitignore000066400000000000000000000000321411177306500173570ustar00rootroot00000000000000.eunit .rebar _build ebin erlang-poolboy-1.5.2+dfsg/.travis.yml000066400000000000000000000002531411177306500175050ustar00rootroot00000000000000language: erlang otp_release: - 21.0.2 - 20.3 - 19.3 - 18.3 - 17.4 - 17.3 - 17.1 - 17.0 - R16B03-1 - R16B03 - R16B02 - R16B01 - R16B sudo: false erlang-poolboy-1.5.2+dfsg/LICENSE000066400000000000000000000014071411177306500164030ustar00rootroot00000000000000ISC License Copyright (c) 2014, Devin Alexander Torres Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. erlang-poolboy-1.5.2+dfsg/Makefile000066400000000000000000000003551411177306500170370ustar00rootroot00000000000000REBAR = $(shell command -v rebar3 || echo ./rebar3) .PHONY: all compile test qc clean all: compile compile: @$(REBAR) compile test: @$(REBAR) eunit qc: compile @$(REBAR) eqc clean: @$(REBAR) clean dialyze: @$(REBAR) dialyzer erlang-poolboy-1.5.2+dfsg/README.md000066400000000000000000000111231411177306500166510ustar00rootroot00000000000000# Poolboy - A hunky Erlang worker pool factory [![Build Status](https://api.travis-ci.org/devinus/poolboy.svg?branch=master)](https://travis-ci.org/devinus/poolboy) [![Support via Gratipay](https://cdn.rawgit.com/gratipay/gratipay-badge/2.3.0/dist/gratipay.png)](https://gratipay.com/devinus/) Poolboy is a **lightweight**, **generic** pooling library for Erlang with a focus on **simplicity**, **performance**, and **rock-solid** disaster recovery. ## Usage ```erl-sh 1> Worker = poolboy:checkout(PoolName). <0.9001.0> 2> gen_server:call(Worker, Request). ok 3> poolboy:checkin(PoolName, Worker). ok ``` ## Example This is an example application showcasing database connection pools using Poolboy and [epgsql](https://github.com/epgsql/epgsql). ### example.app ```erlang {application, example, [ {description, "An example application"}, {vsn, "0.1"}, {applications, [kernel, stdlib, sasl, crypto, ssl]}, {modules, [example, example_worker]}, {registered, [example]}, {mod, {example, []}}, {env, [ {pools, [ {pool1, [ {size, 10}, {max_overflow, 20} ], [ {hostname, "127.0.0.1"}, {database, "db1"}, {username, "db1"}, {password, "abc123"} ]}, {pool2, [ {size, 5}, {max_overflow, 10} ], [ {hostname, "127.0.0.1"}, {database, "db2"}, {username, "db2"}, {password, "abc123"} ]} ]} ]} ]}. ``` ### example.erl ```erlang -module(example). -behaviour(application). -behaviour(supervisor). -export([start/0, stop/0, squery/2, equery/3]). -export([start/2, stop/1]). -export([init/1]). start() -> application:start(?MODULE). stop() -> application:stop(?MODULE). start(_Type, _Args) -> supervisor:start_link({local, example_sup}, ?MODULE, []). stop(_State) -> ok. init([]) -> {ok, Pools} = application:get_env(example, pools), PoolSpecs = lists:map(fun({Name, SizeArgs, WorkerArgs}) -> PoolArgs = [{name, {local, Name}}, {worker_module, example_worker}] ++ SizeArgs, poolboy:child_spec(Name, PoolArgs, WorkerArgs) end, Pools), {ok, {{one_for_one, 10, 10}, PoolSpecs}}. squery(PoolName, Sql) -> poolboy:transaction(PoolName, fun(Worker) -> gen_server:call(Worker, {squery, Sql}) end). equery(PoolName, Stmt, Params) -> poolboy:transaction(PoolName, fun(Worker) -> gen_server:call(Worker, {equery, Stmt, Params}) end). ``` ### example_worker.erl ```erlang -module(example_worker). -behaviour(gen_server). -behaviour(poolboy_worker). -export([start_link/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {conn}). start_link(Args) -> gen_server:start_link(?MODULE, Args, []). init(Args) -> process_flag(trap_exit, true), Hostname = proplists:get_value(hostname, Args), Database = proplists:get_value(database, Args), Username = proplists:get_value(username, Args), Password = proplists:get_value(password, Args), {ok, Conn} = epgsql:connect(Hostname, Username, Password, [ {database, Database} ]), {ok, #state{conn=Conn}}. handle_call({squery, Sql}, _From, #state{conn=Conn}=State) -> {reply, epgsql:squery(Conn, Sql), State}; handle_call({equery, Stmt, Params}, _From, #state{conn=Conn}=State) -> {reply, epgsql:equery(Conn, Stmt, Params), State}; handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, #state{conn=Conn}) -> ok = epgsql:close(Conn), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. ``` ## Options - `name`: the pool name - `worker_module`: the module that represents the workers - `size`: maximum pool size - `max_overflow`: maximum number of workers created if pool is empty - `strategy`: `lifo` or `fifo`, determines whether checked in workers should be placed first or last in the line of available workers. So, `lifo` operates like a traditional stack; `fifo` like a queue. Default is `lifo`. ## Authors - Devin Torres (devinus) - Andrew Thompson (Vagabond) - Kurt Williams (onkel-dirtus) ## License Poolboy is available in the public domain (see `UNLICENSE`). Poolboy is also optionally available under the ISC license (see `LICENSE`), meant especially for jurisdictions that do not recognize public domain works. erlang-poolboy-1.5.2+dfsg/UNLICENSE000066400000000000000000000022731411177306500166500ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to erlang-poolboy-1.5.2+dfsg/VERSION000066400000000000000000000000061411177306500164400ustar00rootroot000000000000001.5.1 erlang-poolboy-1.5.2+dfsg/rebar.config000066400000000000000000000004301411177306500176530ustar00rootroot00000000000000{erl_opts, [ debug_info, {platform_define, "^R", pre17} ]}. {eunit_opts, [verbose]}. {cover_enabled, true}. {profiles, [ {test, [ {plugins, [ {rebar3_eqc, ".*", {git, "https://github.com/kellymclaughlin/rebar3-eqc-plugin.git", {tag, "0.1.0"}}} ]} ] }]}. erlang-poolboy-1.5.2+dfsg/rebar.lock000066400000000000000000000000041411177306500173330ustar00rootroot00000000000000[]. erlang-poolboy-1.5.2+dfsg/src/000077500000000000000000000000001411177306500161635ustar00rootroot00000000000000erlang-poolboy-1.5.2+dfsg/src/poolboy.app.src000066400000000000000000000005441411177306500211410ustar00rootroot00000000000000{application, poolboy, [ {description, "A hunky Erlang worker pool factory"}, {vsn, "1.5.2"}, {applications, [kernel, stdlib]}, {registered, [poolboy]}, {maintainers, ["Devin Torres", "Andrew Thompson", "Kurt Williams"]}, {licenses, ["Unlicense", "Apache 2.0"]}, {links, [{"GitHub", "https://github.com/devinus/poolboy"}]} ]}. erlang-poolboy-1.5.2+dfsg/src/poolboy.erl000066400000000000000000000302411411177306500203520ustar00rootroot00000000000000%% Poolboy - A hunky Erlang worker pool factory -module(poolboy). -behaviour(gen_server). -export([checkout/1, checkout/2, checkout/3, checkin/2, transaction/2, transaction/3, child_spec/2, child_spec/3, start/1, start/2, start_link/1, start_link/2, stop/1, status/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export_type([pool/0]). -define(TIMEOUT, 5000). -ifdef(pre17). -type pid_queue() :: queue(). -else. -type pid_queue() :: queue:queue(). -endif. -ifdef(OTP_RELEASE). %% this implies 21 or higher -define(EXCEPTION(Class, Reason, Stacktrace), Class:Reason:Stacktrace). -define(GET_STACK(Stacktrace), Stacktrace). -else. -define(EXCEPTION(Class, Reason, _), Class:Reason). -define(GET_STACK(_), erlang:get_stacktrace()). -endif. -type pool() :: Name :: (atom() | pid()) | {Name :: atom(), node()} | {local, Name :: atom()} | {global, GlobalName :: any()} | {via, Module :: atom(), ViaName :: any()}. % Copied from gen:start_ret/0 -type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}. -record(state, { supervisor :: undefined | pid(), workers = [] :: [pid()], waiting :: pid_queue(), monitors :: ets:tid(), size = 5 :: non_neg_integer(), overflow = 0 :: non_neg_integer(), max_overflow = 10 :: non_neg_integer(), strategy = lifo :: lifo | fifo }). -spec checkout(Pool :: pool()) -> pid(). checkout(Pool) -> checkout(Pool, true). -spec checkout(Pool :: pool(), Block :: boolean()) -> pid() | full. checkout(Pool, Block) -> checkout(Pool, Block, ?TIMEOUT). -spec checkout(Pool :: pool(), Block :: boolean(), Timeout :: timeout()) -> pid() | full. checkout(Pool, Block, Timeout) -> CRef = make_ref(), try gen_server:call(Pool, {checkout, CRef, Block}, Timeout) catch ?EXCEPTION(Class, Reason, Stacktrace) -> gen_server:cast(Pool, {cancel_waiting, CRef}), erlang:raise(Class, Reason, ?GET_STACK(Stacktrace)) end. -spec checkin(Pool :: pool(), Worker :: pid()) -> ok. checkin(Pool, Worker) when is_pid(Worker) -> gen_server:cast(Pool, {checkin, Worker}). -spec transaction(Pool :: pool(), Fun :: fun((Worker :: pid()) -> any())) -> any(). transaction(Pool, Fun) -> transaction(Pool, Fun, ?TIMEOUT). -spec transaction(Pool :: pool(), Fun :: fun((Worker :: pid()) -> any()), Timeout :: timeout()) -> any(). transaction(Pool, Fun, Timeout) -> Worker = poolboy:checkout(Pool, true, Timeout), try Fun(Worker) after ok = poolboy:checkin(Pool, Worker) end. -spec child_spec(PoolId :: term(), PoolArgs :: proplists:proplist()) -> supervisor:child_spec(). child_spec(PoolId, PoolArgs) -> child_spec(PoolId, PoolArgs, []). -spec child_spec(PoolId :: term(), PoolArgs :: proplists:proplist(), WorkerArgs :: proplists:proplist()) -> supervisor:child_spec(). child_spec(PoolId, PoolArgs, WorkerArgs) -> {PoolId, {poolboy, start_link, [PoolArgs, WorkerArgs]}, permanent, 5000, worker, [poolboy]}. -spec start(PoolArgs :: proplists:proplist()) -> start_ret(). start(PoolArgs) -> start(PoolArgs, PoolArgs). -spec start(PoolArgs :: proplists:proplist(), WorkerArgs:: proplists:proplist()) -> start_ret(). start(PoolArgs, WorkerArgs) -> start_pool(start, PoolArgs, WorkerArgs). -spec start_link(PoolArgs :: proplists:proplist()) -> start_ret(). start_link(PoolArgs) -> %% for backwards compatability, pass the pool args as the worker args as well start_link(PoolArgs, PoolArgs). -spec start_link(PoolArgs :: proplists:proplist(), WorkerArgs:: proplists:proplist()) -> start_ret(). start_link(PoolArgs, WorkerArgs) -> start_pool(start_link, PoolArgs, WorkerArgs). -spec stop(Pool :: pool()) -> ok. stop(Pool) -> gen_server:call(Pool, stop). -spec status(Pool :: pool()) -> {atom(), integer(), integer(), integer()}. status(Pool) -> gen_server:call(Pool, status). init({PoolArgs, WorkerArgs}) -> process_flag(trap_exit, true), Waiting = queue:new(), Monitors = ets:new(monitors, [private]), init(PoolArgs, WorkerArgs, #state{waiting = Waiting, monitors = Monitors}). init([{worker_module, Mod} | Rest], WorkerArgs, State) when is_atom(Mod) -> {ok, Sup} = poolboy_sup:start_link(Mod, WorkerArgs), init(Rest, WorkerArgs, State#state{supervisor = Sup}); init([{size, Size} | Rest], WorkerArgs, State) when is_integer(Size) -> init(Rest, WorkerArgs, State#state{size = Size}); init([{max_overflow, MaxOverflow} | Rest], WorkerArgs, State) when is_integer(MaxOverflow) -> init(Rest, WorkerArgs, State#state{max_overflow = MaxOverflow}); init([{strategy, lifo} | Rest], WorkerArgs, State) -> init(Rest, WorkerArgs, State#state{strategy = lifo}); init([{strategy, fifo} | Rest], WorkerArgs, State) -> init(Rest, WorkerArgs, State#state{strategy = fifo}); init([_ | Rest], WorkerArgs, State) -> init(Rest, WorkerArgs, State); init([], _WorkerArgs, #state{size = Size, supervisor = Sup} = State) -> Workers = prepopulate(Size, Sup), {ok, State#state{workers = Workers}}. handle_cast({checkin, Pid}, State = #state{monitors = Monitors}) -> case ets:lookup(Monitors, Pid) of [{Pid, _, MRef}] -> true = erlang:demonitor(MRef), true = ets:delete(Monitors, Pid), NewState = handle_checkin(Pid, State), {noreply, NewState}; [] -> {noreply, State} end; handle_cast({cancel_waiting, CRef}, State) -> case ets:match(State#state.monitors, {'$1', CRef, '$2'}) of [[Pid, MRef]] -> demonitor(MRef, [flush]), true = ets:delete(State#state.monitors, Pid), NewState = handle_checkin(Pid, State), {noreply, NewState}; [] -> Cancel = fun({_, Ref, MRef}) when Ref =:= CRef -> demonitor(MRef, [flush]), false; (_) -> true end, Waiting = queue:filter(Cancel, State#state.waiting), {noreply, State#state{waiting = Waiting}} end; handle_cast(_Msg, State) -> {noreply, State}. handle_call({checkout, CRef, Block}, {FromPid, _} = From, State) -> #state{supervisor = Sup, workers = Workers, monitors = Monitors, overflow = Overflow, max_overflow = MaxOverflow} = State, case Workers of [Pid | Left] -> MRef = erlang:monitor(process, FromPid), true = ets:insert(Monitors, {Pid, CRef, MRef}), {reply, Pid, State#state{workers = Left}}; [] when MaxOverflow > 0, Overflow < MaxOverflow -> {Pid, MRef} = new_worker(Sup, FromPid), true = ets:insert(Monitors, {Pid, CRef, MRef}), {reply, Pid, State#state{overflow = Overflow + 1}}; [] when Block =:= false -> {reply, full, State}; [] -> MRef = erlang:monitor(process, FromPid), Waiting = queue:in({From, CRef, MRef}, State#state.waiting), {noreply, State#state{waiting = Waiting}} end; handle_call(status, _From, State) -> #state{workers = Workers, monitors = Monitors, overflow = Overflow} = State, StateName = state_name(State), {reply, {StateName, length(Workers), Overflow, ets:info(Monitors, size)}, State}; handle_call(get_avail_workers, _From, State) -> Workers = State#state.workers, {reply, Workers, State}; handle_call(get_all_workers, _From, State) -> Sup = State#state.supervisor, WorkerList = supervisor:which_children(Sup), {reply, WorkerList, State}; handle_call(get_all_monitors, _From, State) -> Monitors = ets:select(State#state.monitors, [{{'$1', '_', '$2'}, [], [{{'$1', '$2'}}]}]), {reply, Monitors, State}; handle_call(stop, _From, State) -> {stop, normal, ok, State}; handle_call(_Msg, _From, State) -> Reply = {error, invalid_message}, {reply, Reply, State}. handle_info({'DOWN', MRef, _, _, _}, State) -> case ets:match(State#state.monitors, {'$1', '_', MRef}) of [[Pid]] -> true = ets:delete(State#state.monitors, Pid), NewState = handle_checkin(Pid, State), {noreply, NewState}; [] -> Waiting = queue:filter(fun ({_, _, R}) -> R =/= MRef end, State#state.waiting), {noreply, State#state{waiting = Waiting}} end; handle_info({'EXIT', Pid, _Reason}, State) -> #state{supervisor = Sup, monitors = Monitors} = State, case ets:lookup(Monitors, Pid) of [{Pid, _, MRef}] -> true = erlang:demonitor(MRef), true = ets:delete(Monitors, Pid), NewState = handle_worker_exit(Pid, State), {noreply, NewState}; [] -> case lists:member(Pid, State#state.workers) of true -> W = lists:filter(fun (P) -> P =/= Pid end, State#state.workers), {noreply, State#state{workers = [new_worker(Sup) | W]}}; false -> {noreply, State} end end; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, State) -> ok = lists:foreach(fun (W) -> unlink(W) end, State#state.workers), true = exit(State#state.supervisor, shutdown), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. start_pool(StartFun, PoolArgs, WorkerArgs) -> case proplists:get_value(name, PoolArgs) of undefined -> gen_server:StartFun(?MODULE, {PoolArgs, WorkerArgs}, []); Name -> gen_server:StartFun(Name, ?MODULE, {PoolArgs, WorkerArgs}, []) end. new_worker(Sup) -> {ok, Pid} = supervisor:start_child(Sup, []), true = link(Pid), Pid. new_worker(Sup, FromPid) -> Pid = new_worker(Sup), Ref = erlang:monitor(process, FromPid), {Pid, Ref}. dismiss_worker(Sup, Pid) -> true = unlink(Pid), supervisor:terminate_child(Sup, Pid). prepopulate(N, _Sup) when N < 1 -> []; prepopulate(N, Sup) -> prepopulate(N, Sup, []). prepopulate(0, _Sup, Workers) -> Workers; prepopulate(N, Sup, Workers) -> prepopulate(N-1, Sup, [new_worker(Sup) | Workers]). handle_checkin(Pid, State) -> #state{supervisor = Sup, waiting = Waiting, monitors = Monitors, overflow = Overflow, strategy = Strategy} = State, case queue:out(Waiting) of {{value, {From, CRef, MRef}}, Left} -> true = ets:insert(Monitors, {Pid, CRef, MRef}), gen_server:reply(From, Pid), State#state{waiting = Left}; {empty, Empty} when Overflow > 0 -> ok = dismiss_worker(Sup, Pid), State#state{waiting = Empty, overflow = Overflow - 1}; {empty, Empty} -> Workers = case Strategy of lifo -> [Pid | State#state.workers]; fifo -> State#state.workers ++ [Pid] end, State#state{workers = Workers, waiting = Empty, overflow = 0} end. handle_worker_exit(Pid, State) -> #state{supervisor = Sup, monitors = Monitors, overflow = Overflow} = State, case queue:out(State#state.waiting) of {{value, {From, CRef, MRef}}, LeftWaiting} -> NewWorker = new_worker(State#state.supervisor), true = ets:insert(Monitors, {NewWorker, CRef, MRef}), gen_server:reply(From, NewWorker), State#state{waiting = LeftWaiting}; {empty, Empty} when Overflow > 0 -> State#state{overflow = Overflow - 1, waiting = Empty}; {empty, Empty} -> Workers = [new_worker(Sup) | lists:filter(fun (P) -> P =/= Pid end, State#state.workers)], State#state{workers = Workers, waiting = Empty} end. state_name(State = #state{overflow = Overflow}) when Overflow < 1 -> #state{max_overflow = MaxOverflow, workers = Workers} = State, case length(Workers) == 0 of true when MaxOverflow < 1 -> full; true -> overflow; false -> ready end; state_name(#state{overflow = MaxOverflow, max_overflow = MaxOverflow}) -> full; state_name(_State) -> overflow. erlang-poolboy-1.5.2+dfsg/src/poolboy_sup.erl000066400000000000000000000005441411177306500212440ustar00rootroot00000000000000%% Poolboy - A hunky Erlang worker pool factory -module(poolboy_sup). -behaviour(supervisor). -export([start_link/2, init/1]). start_link(Mod, Args) -> supervisor:start_link(?MODULE, {Mod, Args}). init({Mod, Args}) -> {ok, {{simple_one_for_one, 0, 1}, [{Mod, {Mod, start_link, [Args]}, temporary, 5000, worker, [Mod]}]}}. erlang-poolboy-1.5.2+dfsg/src/poolboy_worker.erl000066400000000000000000000005251411177306500217450ustar00rootroot00000000000000%% Poolboy - A hunky Erlang worker pool factory -module(poolboy_worker). -callback start_link(WorkerArgs) -> {ok, Pid} | {error, {already_started, Pid}} | {error, Reason} when WorkerArgs :: proplists:proplist(), Pid :: pid(), Reason :: term(). erlang-poolboy-1.5.2+dfsg/test/000077500000000000000000000000001411177306500163535ustar00rootroot00000000000000erlang-poolboy-1.5.2+dfsg/test/poolboy_eqc.erl000066400000000000000000000212211411177306500213700ustar00rootroot00000000000000-module(poolboy_eqc). -compile([export_all, nowarn_export_all]). -ifdef(TEST). -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_statem.hrl"). -include_lib("eunit/include/eunit.hrl"). poolboy_test_() -> {timeout, 20, fun() -> ?assert(eqc:quickcheck(eqc:testing_time(4, poolboy_eqc:prop_sequential()))), ?assert(eqc:quickcheck(eqc:testing_time(4, poolboy_eqc:prop_parallel()))) end }. -record(state, { pid, size, max_overflow, checked_out = [] }). initial_state() -> #state{}. command(S) -> oneof( [{call, ?MODULE, start_poolboy, make_args(S, nat(), nat())} || S#state.pid == undefined] ++ [{call, ?MODULE, stop_poolboy, [S#state.pid]} || S#state.pid /= undefined] ++ [{call, ?MODULE, checkout_nonblock, [S#state.pid]} || S#state.pid /= undefined] ++ %% checkout shrinks to checkout_nonblock so we can simplify counterexamples [{call, ?MODULE, ?SHRINK(checkout_block, [checkout_nonblock]), [S#state.pid]} || S#state.pid /= undefined] ++ [{call, ?MODULE, checkin, [S#state.pid, fault({call, ?MODULE, spawn_process, []}, elements(S#state.checked_out))]} || S#state.pid /= undefined, S#state.checked_out /= []] ++ [{call, ?MODULE, kill_worker, [elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++ [{call, ?MODULE, kill_idle_worker, [S#state.pid]} || S#state.pid /= undefined] ++ [{call, ?MODULE, spurious_exit, [S#state.pid]} || S#state.pid /= undefined] ). make_args(_S, Size, Overflow) -> [[{size, Size}, {max_overflow, Overflow}, {worker_module, poolboy_test_worker}, {name, {local, poolboy_eqc}}]]. spawn_process() -> {spawn(fun() -> timer:sleep(5000) end), self()}. spawn_linked_process(Pool) -> Parent = self(), Pid = spawn(fun() -> link(Pool), Parent ! {linked, self()}, timer:sleep(5000) end), receive {linked, Pid} -> Pid end. start_poolboy(Args) -> {ok, Pid} = poolboy:start_link(Args), Pid. stop_poolboy(Pid) -> gen_server:call(Pid, stop), timer:sleep(1). checkout_nonblock(Pool) -> {poolboy:checkout(Pool, false), self()}. checkout_block(Pool) -> {catch(poolboy:checkout(Pool, true, 100)), self()}. checkin(Pool, {Worker, _}) -> Res = poolboy:checkin(Pool, Worker), gen_server:call(Pool, get_avail_workers), Res. kill_worker({Worker, _}) -> exit(Worker, kill), timer:sleep(1), Worker. kill_idle_worker(Pool) -> Pid = poolboy:checkout(Pool, false), case Pid of _ when is_pid(Pid) -> poolboy:checkin(Pool, Pid), kill_worker({Pid, self()}); _ -> timer:sleep(1), kill_idle_worker(Pool) end. spurious_exit(Pool) -> Pid = spawn_linked_process(Pool), exit(Pid, kill). precondition(S,{call,_,start_poolboy,_}) -> %% only start new pool when old one is stopped S#state.pid == undefined; precondition(S,_) when S#state.pid == undefined -> %% all other states need a running pool false; precondition(S, {call, _, kill_worker, [Pid]}) -> lists:member(Pid, S#state.checked_out); precondition(S,{call,_,kill_idle_worker,[_Pool]}) -> length(S#state.checked_out) < S#state.size; precondition(S,{call,_,checkin,[_Pool, Pid]}) -> lists:member(Pid, S#state.checked_out); precondition(_S,{call,_,_,_}) -> true. %% check model state against internal state, only used in sequential tests invariant(S = #state{pid=Pid},_) when Pid /= undefined -> State = if length(S#state.checked_out) == S#state.size + S#state.max_overflow -> full; length(S#state.checked_out) >= S#state.size -> overflow; true -> ready end, Workers = max(0, S#state.size - length(S#state.checked_out)), OverFlow = max(0, length(S#state.checked_out) - S#state.size), Monitors = length(S#state.checked_out), RealStatus = gen_server:call(Pid, status), case RealStatus == {State, Workers, OverFlow, Monitors} of true -> true; _ -> {wrong_state, RealStatus, {State, Workers, OverFlow, Monitors}} end; invariant(_,_) -> true. %% what states block blocking(S, {call, _, checkout_block, _}) -> %% blocking checkout can block if we expect a checkout to fail not checkout_ok(S); blocking(_, _) -> false. postcondition(S,{call,_,checkout_block,[_Pool]},R) -> case R of {{'EXIT', {timeout, _}}, _} -> case length(S#state.checked_out) >= S#state.size + S#state.max_overflow of true -> true; _ -> {checkout_block, R} end; _ -> case length(S#state.checked_out) < S#state.size + S#state.max_overflow of true -> true; _ -> {checkout_block, R} end end; postcondition(S,{call,_,checkout_nonblock,[_Pool]},R) -> case R of {full, _} -> case length(S#state.checked_out) >= S#state.size + S#state.max_overflow of true -> true; _ -> {checkout_nonblock, R} end; _ -> case length(S#state.checked_out) < S#state.size + S#state.max_overflow of true -> true; _ -> {checkout_block, R} end end; postcondition(_S, {call,_,checkin,_}, R) -> case R of ok -> true; _ -> {checkin, R} end; postcondition(_S,{call,_,_,_},_R) -> true. next_state(S,V,{call,_,start_poolboy, [Args]}) -> S#state{pid=V, size=proplists:get_value(size, Args), max_overflow=proplists:get_value(max_overflow, Args) }; next_state(S,_V,{call,_,stop_poolboy, [_Args]}) -> S#state{pid=undefined, checked_out=[]}; next_state(S,V,{call,_,checkout_block,_}) -> %% if the model says the checkout worked, store the result case checkout_ok(S) of false -> S; _ -> S#state{checked_out=S#state.checked_out++[V]} end; next_state(S,V,{call,_,checkout_nonblock,_}) -> %% if the model says the checkout worked, store the result case checkout_ok(S) of false -> S; _ -> S#state{checked_out=S#state.checked_out++[V]} end; next_state(S,_V,{call, _, checkin, [_Pool, Worker]}) -> S#state{checked_out=S#state.checked_out -- [Worker]}; next_state(S,_V,{call, _, kill_worker, [Worker]}) -> S#state{checked_out=S#state.checked_out -- [Worker]}; next_state(S,_V,{call, _, kill_idle_worker, [_Pool]}) -> S; next_state(S,_V,{call, _, spurious_exit, [_Pool]}) -> S; next_state(S,V,{call, erlang, self, []}) -> %% added after test generation, values are never symbolic S#state{checked_out=[{Worker, Pid} || {Worker, Pid} <- S#state.checked_out, Pid /= V]}. prop_sequential() -> fault_rate(1, 10, ?FORALL(Cmds,commands(?MODULE), ?TRAPEXIT( aggregate(command_names(Cmds), begin {H,S,Res} = run_commands(?MODULE,Cmds), catch(stop_poolboy(whereis(poolboy_eqc))), ?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n~p\n", [H,S,Res, zip(Cmds, [Y || {_, Y} <- H])]), Res == ok) end)))). prop_parallel() -> fault_rate(1, 10, ?FORALL(Cmds={Seq,Par},parallel_commands(?MODULE), ?TRAPEXIT( aggregate(command_names(Cmds), begin NewPar = [P ++ [{set, {var, 0}, {call, erlang, self, []}}] || P <- Par], {H,S,Res} = run_parallel_commands(?MODULE,{Seq,NewPar}), catch(stop_poolboy(whereis(poolboy_eqc))), ?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n", [H,S,Res]), Res == ok) end)))). checkout_ok(S) -> length(S#state.checked_out) < S#state.size + S#state.max_overflow. -endif. -endif. erlang-poolboy-1.5.2+dfsg/test/poolboy_test_worker.erl000066400000000000000000000012061411177306500231710ustar00rootroot00000000000000-module(poolboy_test_worker). -behaviour(gen_server). -behaviour(poolboy_worker). -export([start_link/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). start_link(_Args) -> gen_server:start_link(?MODULE, [], []). init([]) -> {ok, undefined}. handle_call(die, _From, State) -> {stop, {error, died}, dead, State}; handle_call(_Event, _From, State) -> {reply, ok, State}. handle_cast(_Event, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. erlang-poolboy-1.5.2+dfsg/test/poolboy_tests.erl000066400000000000000000000462311411177306500217720ustar00rootroot00000000000000-module(poolboy_tests). -include_lib("eunit/include/eunit.hrl"). pool_test_() -> {foreach, fun() -> error_logger:tty(false) end, fun(_) -> case whereis(poolboy_test) of undefined -> ok; Pid -> pool_call(Pid, stop) end, error_logger:tty(true) end, [ {<<"Basic pool operations">>, fun pool_startup/0 }, {<<"Pool overflow should work">>, fun pool_overflow/0 }, {<<"Pool behaves when empty">>, fun pool_empty/0 }, {<<"Pool behaves when empty and oveflow is disabled">>, fun pool_empty_no_overflow/0 }, {<<"Pool behaves on worker death">>, fun worker_death/0 }, {<<"Pool behaves when full and a worker dies">>, fun worker_death_while_full/0 }, {<<"Pool behaves when full, a worker dies and overflow disabled">>, fun worker_death_while_full_no_overflow/0 }, {<<"Non-blocking pool behaves when full and overflow disabled">>, fun pool_full_nonblocking_no_overflow/0 }, {<<"Non-blocking pool behaves when full">>, fun pool_full_nonblocking/0 }, {<<"Pool behaves on owner death">>, fun owner_death/0 }, {<<"Worker checked-in after an exception in a transaction">>, fun checkin_after_exception_in_transaction/0 }, {<<"Pool returns status">>, fun pool_returns_status/0 }, {<<"Pool demonitors previously waiting processes">>, fun demonitors_previously_waiting_processes/0 }, {<<"Pool demonitors when a checkout is cancelled">>, fun demonitors_when_checkout_cancelled/0 }, {<<"Check that LIFO is the default strategy">>, fun default_strategy_lifo/0 }, {<<"Check LIFO strategy">>, fun lifo_strategy/0 }, {<<"Check FIFO strategy">>, fun fifo_strategy/0 }, {<<"Pool reuses waiting monitor when a worker exits">>, fun reuses_waiting_monitor_on_worker_exit/0 }, {<<"Recover from timeout without exit handling">>, fun transaction_timeout_without_exit/0}, {<<"Recover from transaction timeout">>, fun transaction_timeout/0} ] }. %% Tell a worker to exit and await its impending doom. kill_worker(Pid) -> erlang:monitor(process, Pid), pool_call(Pid, die), receive {'DOWN', _, process, Pid, _} -> ok end. checkin_worker(Pid, Worker) -> %% There's no easy way to wait for a checkin to complete, because it's %% async and the supervisor may kill the process if it was an overflow %% worker. The only solution seems to be a nasty hardcoded sleep. poolboy:checkin(Pid, Worker), timer:sleep(500). transaction_timeout_without_exit() -> {ok, Pid} = new_pool(1, 0), ?assertEqual({ready,1,0,0}, pool_call(Pid, status)), WorkerList = pool_call(Pid, get_all_workers), ?assertMatch([_], WorkerList), spawn(poolboy, transaction, [Pid, fun(Worker) -> ok = pool_call(Worker, work) end, 0]), timer:sleep(100), ?assertEqual(WorkerList, pool_call(Pid, get_all_workers)), ?assertEqual({ready,1,0,0}, pool_call(Pid, status)). transaction_timeout() -> {ok, Pid} = new_pool(1, 0), ?assertEqual({ready,1,0,0}, pool_call(Pid, status)), WorkerList = pool_call(Pid, get_all_workers), ?assertMatch([_], WorkerList), ?assertExit( {timeout, _}, poolboy:transaction(Pid, fun(Worker) -> ok = pool_call(Worker, work) end, 0)), ?assertEqual(WorkerList, pool_call(Pid, get_all_workers)), ?assertEqual({ready,1,0,0}, pool_call(Pid, status)). pool_startup() -> %% Check basic pool operation. {ok, Pid} = new_pool(10, 5), ?assertEqual(10, length(pool_call(Pid, get_avail_workers))), poolboy:checkout(Pid), ?assertEqual(9, length(pool_call(Pid, get_avail_workers))), Worker = poolboy:checkout(Pid), ?assertEqual(8, length(pool_call(Pid, get_avail_workers))), checkin_worker(Pid, Worker), ?assertEqual(9, length(pool_call(Pid, get_avail_workers))), ?assertEqual(1, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). pool_overflow() -> %% Check that the pool overflows properly. {ok, Pid} = new_pool(5, 5), Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(7, length(pool_call(Pid, get_all_workers))), [A, B, C, D, E, F, G] = Workers, checkin_worker(Pid, A), checkin_worker(Pid, B), ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, C), checkin_worker(Pid, D), ?assertEqual(2, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, E), checkin_worker(Pid, F), ?assertEqual(4, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, G), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(0, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). pool_empty() -> %% Checks that the the pool handles the empty condition correctly when %% overflow is enabled. {ok, Pid} = new_pool(5, 2), Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(7, length(pool_call(Pid, get_all_workers))), [A, B, C, D, E, F, G] = Workers, Self = self(), spawn(fun() -> Worker = poolboy:checkout(Pid), Self ! got_worker, checkin_worker(Pid, Worker) end), %% Spawned process should block waiting for worker to be available. receive got_worker -> ?assert(false) after 500 -> ?assert(true) end, checkin_worker(Pid, A), checkin_worker(Pid, B), %% Spawned process should have been able to obtain a worker. receive got_worker -> ?assert(true) after 500 -> ?assert(false) end, ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, C), checkin_worker(Pid, D), ?assertEqual(2, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, E), checkin_worker(Pid, F), ?assertEqual(4, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, G), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(0, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). pool_empty_no_overflow() -> %% Checks the pool handles the empty condition properly when overflow is %% disabled. {ok, Pid} = new_pool(5, 0), Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 4)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), [A, B, C, D, E] = Workers, Self = self(), spawn(fun() -> Worker = poolboy:checkout(Pid), Self ! got_worker, checkin_worker(Pid, Worker) end), %% Spawned process should block waiting for worker to be available. receive got_worker -> ?assert(false) after 500 -> ?assert(true) end, checkin_worker(Pid, A), checkin_worker(Pid, B), %% Spawned process should have been able to obtain a worker. receive got_worker -> ?assert(true) after 500 -> ?assert(false) end, ?assertEqual(2, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, C), checkin_worker(Pid, D), ?assertEqual(4, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), checkin_worker(Pid, E), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(0, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). worker_death() -> %% Check that dead workers are only restarted when the pool is not full %% and the overflow count is 0. Meaning, don't restart overflow workers. {ok, Pid} = new_pool(5, 2), Worker = poolboy:checkout(Pid), kill_worker(Worker), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), [A, B, C|_Workers] = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(7, length(pool_call(Pid, get_all_workers))), kill_worker(A), ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(6, length(pool_call(Pid, get_all_workers))), kill_worker(B), kill_worker(C), ?assertEqual(1, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(4, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). worker_death_while_full() -> %% Check that if a worker dies while the pool is full and there is a %% queued checkout, a new worker is started and the checkout serviced. %% If there are no queued checkouts, a new worker is not started. {ok, Pid} = new_pool(5, 2), Worker = poolboy:checkout(Pid), kill_worker(Worker), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), [A, B|_Workers] = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(7, length(pool_call(Pid, get_all_workers))), Self = self(), spawn(fun() -> poolboy:checkout(Pid), Self ! got_worker, %% XXX: Don't release the worker. We want to also test what happens %% when the worker pool is full and a worker dies with no queued %% checkouts. timer:sleep(5000) end), %% Spawned process should block waiting for worker to be available. receive got_worker -> ?assert(false) after 500 -> ?assert(true) end, kill_worker(A), %% Spawned process should have been able to obtain a worker. receive got_worker -> ?assert(true) after 1000 -> ?assert(false) end, kill_worker(B), ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(6, length(pool_call(Pid, get_all_workers))), ?assertEqual(6, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). worker_death_while_full_no_overflow() -> %% Check that if a worker dies while the pool is full and there's no %% overflow, a new worker is started unconditionally and any queued %% checkouts are serviced. {ok, Pid} = new_pool(5, 0), Worker = poolboy:checkout(Pid), kill_worker(Worker), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), [A, B, C|_Workers] = [poolboy:checkout(Pid) || _ <- lists:seq(0, 4)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), Self = self(), spawn(fun() -> poolboy:checkout(Pid), Self ! got_worker, %% XXX: Do not release, need to also test when worker dies and no %% checkouts queued. timer:sleep(5000) end), %% Spawned process should block waiting for worker to be available. receive got_worker -> ?assert(false) after 500 -> ?assert(true) end, kill_worker(A), %% Spawned process should have been able to obtain a worker. receive got_worker -> ?assert(true) after 1000 -> ?assert(false) end, kill_worker(B), ?assertEqual(1, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), kill_worker(C), ?assertEqual(2, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(3, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). pool_full_nonblocking_no_overflow() -> %% Check that when the pool is full, checkouts return 'full' when the %% option to use non-blocking checkouts is used. {ok, Pid} = new_pool(5, 0), Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 4)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(full, poolboy:checkout(Pid, false)), ?assertEqual(full, poolboy:checkout(Pid, false)), A = hd(Workers), checkin_worker(Pid, A), ?assertEqual(A, poolboy:checkout(Pid)), ?assertEqual(5, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). pool_full_nonblocking() -> %% Check that when the pool is full, checkouts return 'full' when the %% option to use non-blocking checkouts is used. {ok, Pid} = new_pool(5, 5), Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 9)], ?assertEqual(0, length(pool_call(Pid, get_avail_workers))), ?assertEqual(10, length(pool_call(Pid, get_all_workers))), ?assertEqual(full, poolboy:checkout(Pid, false)), A = hd(Workers), checkin_worker(Pid, A), NewWorker = poolboy:checkout(Pid, false), ?assertEqual(false, is_process_alive(A)), %% Overflow workers get shutdown ?assert(is_pid(NewWorker)), ?assertEqual(full, poolboy:checkout(Pid, false)), ?assertEqual(10, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). owner_death() -> %% Check that a dead owner (a process that dies with a worker checked out) %% causes the pool to dismiss the worker and prune the state space. {ok, Pid} = new_pool(5, 5), spawn(fun() -> poolboy:checkout(Pid), receive after 500 -> exit(normal) end end), timer:sleep(1000), ?assertEqual(5, length(pool_call(Pid, get_avail_workers))), ?assertEqual(5, length(pool_call(Pid, get_all_workers))), ?assertEqual(0, length(pool_call(Pid, get_all_monitors))), ok = pool_call(Pid, stop). checkin_after_exception_in_transaction() -> {ok, Pool} = new_pool(2, 0), ?assertEqual(2, length(pool_call(Pool, get_avail_workers))), Tx = fun(Worker) -> ?assert(is_pid(Worker)), ?assertEqual(1, length(pool_call(Pool, get_avail_workers))), throw(it_on_the_ground), ?assert(false) end, try poolboy:transaction(Pool, Tx) catch throw:it_on_the_ground -> ok end, ?assertEqual(2, length(pool_call(Pool, get_avail_workers))), ok = pool_call(Pool, stop). pool_returns_status() -> {ok, Pool} = new_pool(2, 0), ?assertEqual({ready, 2, 0, 0}, poolboy:status(Pool)), poolboy:checkout(Pool), ?assertEqual({ready, 1, 0, 1}, poolboy:status(Pool)), poolboy:checkout(Pool), ?assertEqual({full, 0, 0, 2}, poolboy:status(Pool)), ok = pool_call(Pool, stop), {ok, Pool2} = new_pool(1, 1), ?assertEqual({ready, 1, 0, 0}, poolboy:status(Pool2)), poolboy:checkout(Pool2), ?assertEqual({overflow, 0, 0, 1}, poolboy:status(Pool2)), poolboy:checkout(Pool2), ?assertEqual({full, 0, 1, 2}, poolboy:status(Pool2)), ok = pool_call(Pool2, stop), {ok, Pool3} = new_pool(0, 2), ?assertEqual({overflow, 0, 0, 0}, poolboy:status(Pool3)), poolboy:checkout(Pool3), ?assertEqual({overflow, 0, 1, 1}, poolboy:status(Pool3)), poolboy:checkout(Pool3), ?assertEqual({full, 0, 2, 2}, poolboy:status(Pool3)), ok = pool_call(Pool3, stop), {ok, Pool4} = new_pool(0, 0), ?assertEqual({full, 0, 0, 0}, poolboy:status(Pool4)), ok = pool_call(Pool4, stop). demonitors_previously_waiting_processes() -> {ok, Pool} = new_pool(1,0), Self = self(), Pid = spawn(fun() -> W = poolboy:checkout(Pool), Self ! ok, timer:sleep(500), poolboy:checkin(Pool, W), receive ok -> ok end end), receive ok -> ok end, Worker = poolboy:checkout(Pool), ?assertEqual(1, length(get_monitors(Pool))), poolboy:checkin(Pool, Worker), timer:sleep(500), ?assertEqual(0, length(get_monitors(Pool))), Pid ! ok, ok = pool_call(Pool, stop). demonitors_when_checkout_cancelled() -> {ok, Pool} = new_pool(1,0), Self = self(), Pid = spawn(fun() -> poolboy:checkout(Pool), _ = (catch poolboy:checkout(Pool, true, 1000)), Self ! ok, receive ok -> ok end end), timer:sleep(500), ?assertEqual(2, length(get_monitors(Pool))), receive ok -> ok end, ?assertEqual(1, length(get_monitors(Pool))), Pid ! ok, ok = pool_call(Pool, stop). default_strategy_lifo() -> %% Default strategy is LIFO {ok, Pid} = new_pool(2, 0), Worker1 = poolboy:checkout(Pid), ok = poolboy:checkin(Pid, Worker1), Worker1 = poolboy:checkout(Pid), poolboy:stop(Pid). lifo_strategy() -> {ok, Pid} = new_pool(2, 0, lifo), Worker1 = poolboy:checkout(Pid), ok = poolboy:checkin(Pid, Worker1), Worker1 = poolboy:checkout(Pid), poolboy:stop(Pid). fifo_strategy() -> {ok, Pid} = new_pool(2, 0, fifo), Worker1 = poolboy:checkout(Pid), ok = poolboy:checkin(Pid, Worker1), Worker2 = poolboy:checkout(Pid), ?assert(Worker1 =/= Worker2), Worker1 = poolboy:checkout(Pid), poolboy:stop(Pid). reuses_waiting_monitor_on_worker_exit() -> {ok, Pool} = new_pool(1,0), Self = self(), Pid = spawn(fun() -> Worker = poolboy:checkout(Pool), Self ! {worker, Worker}, poolboy:checkout(Pool), receive ok -> ok end end), Worker = receive {worker, Worker1} -> Worker1 end, Ref = monitor(process, Worker), exit(Worker, kill), receive {'DOWN', Ref, _, _, _} -> ok end, ?assertEqual(1, length(get_monitors(Pool))), Pid ! ok, ok = pool_call(Pool, stop). get_monitors(Pid) -> %% Synchronise with the Pid to ensure it has handled all expected work. _ = sys:get_status(Pid), [{monitors, Monitors}] = erlang:process_info(Pid, [monitors]), Monitors. new_pool(Size, MaxOverflow) -> poolboy:start_link([{name, {local, poolboy_test}}, {worker_module, poolboy_test_worker}, {size, Size}, {max_overflow, MaxOverflow}]). new_pool(Size, MaxOverflow, Strategy) -> poolboy:start_link([{name, {local, poolboy_test}}, {worker_module, poolboy_test_worker}, {size, Size}, {max_overflow, MaxOverflow}, {strategy, Strategy}]). pool_call(ServerRef, Request) -> gen_server:call(ServerRef, Request).