1-module(poolboy_tests).
2
3-include_lib("eunit/include/eunit.hrl").
4
5pool_test_() ->
6    {foreach,
7        fun() ->
8            error_logger:tty(false)
9        end,
10        fun(_) ->
11            case whereis(poolboy_test) of
12                undefined -> ok;
13                Pid -> pool_call(Pid, stop)
14            end,
15            error_logger:tty(true)
16        end,
17        [
18            {<<"Basic pool operations">>,
19                fun pool_startup/0
20            },
21            {<<"Pool overflow should work">>,
22                fun pool_overflow/0
23            },
24            {<<"Pool behaves when empty">>,
25                fun pool_empty/0
26            },
27            {<<"Pool behaves when empty and oveflow is disabled">>,
28                fun pool_empty_no_overflow/0
29            },
30            {<<"Pool behaves on worker death">>,
31                fun worker_death/0
32            },
33            {<<"Pool behaves when full and a worker dies">>,
34                fun worker_death_while_full/0
35            },
36            {<<"Pool behaves when full, a worker dies and overflow disabled">>,
37                fun worker_death_while_full_no_overflow/0
38            },
39            {<<"Non-blocking pool behaves when full and overflow disabled">>,
40                fun pool_full_nonblocking_no_overflow/0
41            },
42            {<<"Non-blocking pool behaves when full">>,
43                fun pool_full_nonblocking/0
44            },
45            {<<"Pool behaves on owner death">>,
46                fun owner_death/0
47            },
48            {<<"Worker checked-in after an exception in a transaction">>,
49                fun checkin_after_exception_in_transaction/0
50            },
51            {<<"Pool returns status">>,
52                fun pool_returns_status/0
53            },
54            {<<"Pool demonitors previously waiting processes">>,
55                fun demonitors_previously_waiting_processes/0
56            },
57            {<<"Pool demonitors when a checkout is cancelled">>,
58                fun demonitors_when_checkout_cancelled/0
59            },
60            {<<"Check that LIFO is the default strategy">>,
61                fun default_strategy_lifo/0
62            },
63            {<<"Check LIFO strategy">>,
64                fun lifo_strategy/0
65            },
66            {<<"Check FIFO strategy">>,
67                fun fifo_strategy/0
68            },
69            {<<"Pool reuses waiting monitor when a worker exits">>,
70                fun reuses_waiting_monitor_on_worker_exit/0
71            },
72            {<<"Recover from timeout without exit handling">>,
73                fun transaction_timeout_without_exit/0},
74            {<<"Recover from transaction timeout">>,
75                fun transaction_timeout/0}
76        ]
77    }.
78
79%% Tell a worker to exit and await its impending doom.
80kill_worker(Pid) ->
81    erlang:monitor(process, Pid),
82    pool_call(Pid, die),
83    receive
84        {'DOWN', _, process, Pid, _} ->
85            ok
86    end.
87
88checkin_worker(Pid, Worker) ->
89    %% There's no easy way to wait for a checkin to complete, because it's
90    %% async and the supervisor may kill the process if it was an overflow
91    %% worker. The only solution seems to be a nasty hardcoded sleep.
92    poolboy:checkin(Pid, Worker),
93    timer:sleep(500).
94
95
96transaction_timeout_without_exit() ->
97    {ok, Pid} = new_pool(1, 0),
98    ?assertEqual({ready,1,0,0}, pool_call(Pid, status)),
99    WorkerList = pool_call(Pid, get_all_workers),
100    ?assertMatch([_], WorkerList),
101    spawn(poolboy, transaction, [Pid,
102        fun(Worker) ->
103            ok = pool_call(Worker, work)
104        end,
105        0]),
106    timer:sleep(100),
107    ?assertEqual(WorkerList, pool_call(Pid, get_all_workers)),
108    ?assertEqual({ready,1,0,0}, pool_call(Pid, status)).
109
110
111transaction_timeout() ->
112    {ok, Pid} = new_pool(1, 0),
113    ?assertEqual({ready,1,0,0}, pool_call(Pid, status)),
114    WorkerList = pool_call(Pid, get_all_workers),
115    ?assertMatch([_], WorkerList),
116    ?assertExit(
117        {timeout, _},
118        poolboy:transaction(Pid,
119            fun(Worker) ->
120                ok = pool_call(Worker, work)
121            end,
122            0)),
123    ?assertEqual(WorkerList, pool_call(Pid, get_all_workers)),
124    ?assertEqual({ready,1,0,0}, pool_call(Pid, status)).
125
126
127pool_startup() ->
128    %% Check basic pool operation.
129    {ok, Pid} = new_pool(10, 5),
130    ?assertEqual(10, length(pool_call(Pid, get_avail_workers))),
131    poolboy:checkout(Pid),
132    ?assertEqual(9, length(pool_call(Pid, get_avail_workers))),
133    Worker = poolboy:checkout(Pid),
134    ?assertEqual(8, length(pool_call(Pid, get_avail_workers))),
135    checkin_worker(Pid, Worker),
136    ?assertEqual(9, length(pool_call(Pid, get_avail_workers))),
137    ?assertEqual(1, length(pool_call(Pid, get_all_monitors))),
138    ok = pool_call(Pid, stop).
139
140pool_overflow() ->
141    %% Check that the pool overflows properly.
142    {ok, Pid} = new_pool(5, 5),
143    Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)],
144    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
145    ?assertEqual(7, length(pool_call(Pid, get_all_workers))),
146    [A, B, C, D, E, F, G] = Workers,
147    checkin_worker(Pid, A),
148    checkin_worker(Pid, B),
149    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
150    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
151    checkin_worker(Pid, C),
152    checkin_worker(Pid, D),
153    ?assertEqual(2, length(pool_call(Pid, get_avail_workers))),
154    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
155    checkin_worker(Pid, E),
156    checkin_worker(Pid, F),
157    ?assertEqual(4, length(pool_call(Pid, get_avail_workers))),
158    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
159    checkin_worker(Pid, G),
160    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
161    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
162    ?assertEqual(0, length(pool_call(Pid, get_all_monitors))),
163    ok = pool_call(Pid, stop).
164
165pool_empty() ->
166    %% Checks that the the pool handles the empty condition correctly when
167    %% overflow is enabled.
168    {ok, Pid} = new_pool(5, 2),
169    Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)],
170    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
171    ?assertEqual(7, length(pool_call(Pid, get_all_workers))),
172    [A, B, C, D, E, F, G] = Workers,
173    Self = self(),
174    spawn(fun() ->
175        Worker = poolboy:checkout(Pid),
176        Self ! got_worker,
177        checkin_worker(Pid, Worker)
178    end),
179
180    %% Spawned process should block waiting for worker to be available.
181    receive
182        got_worker -> ?assert(false)
183    after
184        500 -> ?assert(true)
185    end,
186    checkin_worker(Pid, A),
187    checkin_worker(Pid, B),
188
189    %% Spawned process should have been able to obtain a worker.
190    receive
191        got_worker -> ?assert(true)
192    after
193        500 -> ?assert(false)
194    end,
195    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
196    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
197    checkin_worker(Pid, C),
198    checkin_worker(Pid, D),
199    ?assertEqual(2, length(pool_call(Pid, get_avail_workers))),
200    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
201    checkin_worker(Pid, E),
202    checkin_worker(Pid, F),
203    ?assertEqual(4, length(pool_call(Pid, get_avail_workers))),
204    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
205    checkin_worker(Pid, G),
206    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
207    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
208    ?assertEqual(0, length(pool_call(Pid, get_all_monitors))),
209    ok = pool_call(Pid, stop).
210
211pool_empty_no_overflow() ->
212    %% Checks the pool handles the empty condition properly when overflow is
213    %% disabled.
214    {ok, Pid} = new_pool(5, 0),
215    Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 4)],
216    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
217    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
218    [A, B, C, D, E] = Workers,
219    Self = self(),
220    spawn(fun() ->
221        Worker = poolboy:checkout(Pid),
222        Self ! got_worker,
223        checkin_worker(Pid, Worker)
224    end),
225
226    %% Spawned process should block waiting for worker to be available.
227    receive
228        got_worker -> ?assert(false)
229    after
230        500 -> ?assert(true)
231    end,
232    checkin_worker(Pid, A),
233    checkin_worker(Pid, B),
234
235    %% Spawned process should have been able to obtain a worker.
236    receive
237        got_worker -> ?assert(true)
238    after
239        500 -> ?assert(false)
240    end,
241    ?assertEqual(2, length(pool_call(Pid, get_avail_workers))),
242    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
243    checkin_worker(Pid, C),
244    checkin_worker(Pid, D),
245    ?assertEqual(4, length(pool_call(Pid, get_avail_workers))),
246    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
247    checkin_worker(Pid, E),
248    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
249    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
250    ?assertEqual(0, length(pool_call(Pid, get_all_monitors))),
251    ok = pool_call(Pid, stop).
252
253worker_death() ->
254    %% Check that dead workers are only restarted when the pool is not full
255    %% and the overflow count is 0. Meaning, don't restart overflow workers.
256    {ok, Pid} = new_pool(5, 2),
257    Worker = poolboy:checkout(Pid),
258    kill_worker(Worker),
259    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
260    [A, B, C|_Workers] = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)],
261    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
262    ?assertEqual(7, length(pool_call(Pid, get_all_workers))),
263    kill_worker(A),
264    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
265    ?assertEqual(6, length(pool_call(Pid, get_all_workers))),
266    kill_worker(B),
267    kill_worker(C),
268    ?assertEqual(1, length(pool_call(Pid, get_avail_workers))),
269    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
270    ?assertEqual(4, length(pool_call(Pid, get_all_monitors))),
271    ok = pool_call(Pid, stop).
272
273worker_death_while_full() ->
274    %% Check that if a worker dies while the pool is full and there is a
275    %% queued checkout, a new worker is started and the checkout serviced.
276    %% If there are no queued checkouts, a new worker is not started.
277    {ok, Pid} = new_pool(5, 2),
278    Worker = poolboy:checkout(Pid),
279    kill_worker(Worker),
280    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
281    [A, B|_Workers] = [poolboy:checkout(Pid) || _ <- lists:seq(0, 6)],
282    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
283    ?assertEqual(7, length(pool_call(Pid, get_all_workers))),
284    Self = self(),
285    spawn(fun() ->
286        poolboy:checkout(Pid),
287        Self ! got_worker,
288        %% XXX: Don't release the worker. We want to also test what happens
289        %% when the worker pool is full and a worker dies with no queued
290        %% checkouts.
291        timer:sleep(5000)
292    end),
293
294    %% Spawned process should block waiting for worker to be available.
295    receive
296        got_worker -> ?assert(false)
297    after
298        500 -> ?assert(true)
299    end,
300    kill_worker(A),
301
302    %% Spawned process should have been able to obtain a worker.
303    receive
304        got_worker -> ?assert(true)
305    after
306        1000 -> ?assert(false)
307    end,
308    kill_worker(B),
309    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
310    ?assertEqual(6, length(pool_call(Pid, get_all_workers))),
311    ?assertEqual(6, length(pool_call(Pid, get_all_monitors))),
312    ok = pool_call(Pid, stop).
313
314worker_death_while_full_no_overflow() ->
315    %% Check that if a worker dies while the pool is full and there's no
316    %% overflow, a new worker is started unconditionally and any queued
317    %% checkouts are serviced.
318    {ok, Pid} = new_pool(5, 0),
319    Worker = poolboy:checkout(Pid),
320    kill_worker(Worker),
321    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
322    [A, B, C|_Workers] = [poolboy:checkout(Pid) || _ <- lists:seq(0, 4)],
323    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
324    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
325    Self = self(),
326    spawn(fun() ->
327        poolboy:checkout(Pid),
328        Self ! got_worker,
329        %% XXX: Do not release, need to also test when worker dies and no
330        %% checkouts queued.
331        timer:sleep(5000)
332    end),
333
334    %% Spawned process should block waiting for worker to be available.
335    receive
336        got_worker -> ?assert(false)
337    after
338        500 -> ?assert(true)
339    end,
340    kill_worker(A),
341
342    %% Spawned process should have been able to obtain a worker.
343    receive
344        got_worker -> ?assert(true)
345    after
346        1000 -> ?assert(false)
347    end,
348    kill_worker(B),
349    ?assertEqual(1, length(pool_call(Pid, get_avail_workers))),
350    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
351    kill_worker(C),
352    ?assertEqual(2, length(pool_call(Pid, get_avail_workers))),
353    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
354    ?assertEqual(3, length(pool_call(Pid, get_all_monitors))),
355    ok = pool_call(Pid, stop).
356
357pool_full_nonblocking_no_overflow() ->
358    %% Check that when the pool is full, checkouts return 'full' when the
359    %% option to use non-blocking checkouts is used.
360    {ok, Pid} = new_pool(5, 0),
361    Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 4)],
362    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
363    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
364    ?assertEqual(full, poolboy:checkout(Pid, false)),
365    ?assertEqual(full, poolboy:checkout(Pid, false)),
366    A = hd(Workers),
367    checkin_worker(Pid, A),
368    ?assertEqual(A, poolboy:checkout(Pid)),
369    ?assertEqual(5, length(pool_call(Pid, get_all_monitors))),
370    ok = pool_call(Pid, stop).
371
372pool_full_nonblocking() ->
373    %% Check that when the pool is full, checkouts return 'full' when the
374    %% option to use non-blocking checkouts is used.
375    {ok, Pid} = new_pool(5, 5),
376    Workers = [poolboy:checkout(Pid) || _ <- lists:seq(0, 9)],
377    ?assertEqual(0, length(pool_call(Pid, get_avail_workers))),
378    ?assertEqual(10, length(pool_call(Pid, get_all_workers))),
379    ?assertEqual(full, poolboy:checkout(Pid, false)),
380    A = hd(Workers),
381    checkin_worker(Pid, A),
382    NewWorker = poolboy:checkout(Pid, false),
383    ?assertEqual(false, is_process_alive(A)), %% Overflow workers get shutdown
384    ?assert(is_pid(NewWorker)),
385    ?assertEqual(full, poolboy:checkout(Pid, false)),
386    ?assertEqual(10, length(pool_call(Pid, get_all_monitors))),
387    ok = pool_call(Pid, stop).
388
389owner_death() ->
390    %% Check that a dead owner (a process that dies with a worker checked out)
391    %% causes the pool to dismiss the worker and prune the state space.
392    {ok, Pid} = new_pool(5, 5),
393    spawn(fun() ->
394        poolboy:checkout(Pid),
395        receive after 500 -> exit(normal) end
396    end),
397    timer:sleep(1000),
398    ?assertEqual(5, length(pool_call(Pid, get_avail_workers))),
399    ?assertEqual(5, length(pool_call(Pid, get_all_workers))),
400    ?assertEqual(0, length(pool_call(Pid, get_all_monitors))),
401    ok = pool_call(Pid, stop).
402
403checkin_after_exception_in_transaction() ->
404    {ok, Pool} = new_pool(2, 0),
405    ?assertEqual(2, length(pool_call(Pool, get_avail_workers))),
406    Tx = fun(Worker) ->
407        ?assert(is_pid(Worker)),
408        ?assertEqual(1, length(pool_call(Pool, get_avail_workers))),
409        throw(it_on_the_ground),
410        ?assert(false)
411    end,
412    try
413        poolboy:transaction(Pool, Tx)
414    catch
415        throw:it_on_the_ground -> ok
416    end,
417    ?assertEqual(2, length(pool_call(Pool, get_avail_workers))),
418    ok = pool_call(Pool, stop).
419
420pool_returns_status() ->
421    {ok, Pool} = new_pool(2, 0),
422    ?assertEqual({ready, 2, 0, 0}, poolboy:status(Pool)),
423    poolboy:checkout(Pool),
424    ?assertEqual({ready, 1, 0, 1}, poolboy:status(Pool)),
425    poolboy:checkout(Pool),
426    ?assertEqual({full, 0, 0, 2}, poolboy:status(Pool)),
427    ok = pool_call(Pool, stop),
428
429    {ok, Pool2} = new_pool(1, 1),
430    ?assertEqual({ready, 1, 0, 0}, poolboy:status(Pool2)),
431    poolboy:checkout(Pool2),
432    ?assertEqual({overflow, 0, 0, 1}, poolboy:status(Pool2)),
433    poolboy:checkout(Pool2),
434    ?assertEqual({full, 0, 1, 2}, poolboy:status(Pool2)),
435    ok = pool_call(Pool2, stop),
436
437    {ok, Pool3} = new_pool(0, 2),
438    ?assertEqual({overflow, 0, 0, 0}, poolboy:status(Pool3)),
439    poolboy:checkout(Pool3),
440    ?assertEqual({overflow, 0, 1, 1}, poolboy:status(Pool3)),
441    poolboy:checkout(Pool3),
442    ?assertEqual({full, 0, 2, 2}, poolboy:status(Pool3)),
443    ok = pool_call(Pool3, stop),
444
445    {ok, Pool4} = new_pool(0, 0),
446    ?assertEqual({full, 0, 0, 0}, poolboy:status(Pool4)),
447    ok = pool_call(Pool4, stop).
448
449demonitors_previously_waiting_processes() ->
450    {ok, Pool} = new_pool(1,0),
451    Self = self(),
452    Pid = spawn(fun() ->
453        W = poolboy:checkout(Pool),
454        Self ! ok,
455        timer:sleep(500),
456        poolboy:checkin(Pool, W),
457        receive ok -> ok end
458    end),
459    receive ok -> ok end,
460    Worker = poolboy:checkout(Pool),
461    ?assertEqual(1, length(get_monitors(Pool))),
462    poolboy:checkin(Pool, Worker),
463    timer:sleep(500),
464    ?assertEqual(0, length(get_monitors(Pool))),
465    Pid ! ok,
466    ok = pool_call(Pool, stop).
467
468demonitors_when_checkout_cancelled() ->
469    {ok, Pool} = new_pool(1,0),
470    Self = self(),
471    Pid = spawn(fun() ->
472        poolboy:checkout(Pool),
473        _ = (catch poolboy:checkout(Pool, true, 1000)),
474        Self ! ok,
475        receive ok -> ok end
476    end),
477    timer:sleep(500),
478    ?assertEqual(2, length(get_monitors(Pool))),
479    receive ok -> ok end,
480    ?assertEqual(1, length(get_monitors(Pool))),
481    Pid ! ok,
482    ok = pool_call(Pool, stop).
483
484default_strategy_lifo() ->
485    %% Default strategy is LIFO
486    {ok, Pid} = new_pool(2, 0),
487    Worker1 = poolboy:checkout(Pid),
488    ok = poolboy:checkin(Pid, Worker1),
489    Worker1 = poolboy:checkout(Pid),
490    poolboy:stop(Pid).
491
492lifo_strategy() ->
493    {ok, Pid} = new_pool(2, 0, lifo),
494    Worker1 = poolboy:checkout(Pid),
495    ok = poolboy:checkin(Pid, Worker1),
496    Worker1 = poolboy:checkout(Pid),
497    poolboy:stop(Pid).
498
499fifo_strategy() ->
500    {ok, Pid} = new_pool(2, 0, fifo),
501    Worker1 = poolboy:checkout(Pid),
502    ok = poolboy:checkin(Pid, Worker1),
503    Worker2 = poolboy:checkout(Pid),
504    ?assert(Worker1 =/= Worker2),
505    Worker1 = poolboy:checkout(Pid),
506    poolboy:stop(Pid).
507
508reuses_waiting_monitor_on_worker_exit() ->
509    {ok, Pool} = new_pool(1,0),
510
511    Self = self(),
512    Pid = spawn(fun() ->
513        Worker = poolboy:checkout(Pool),
514        Self ! {worker, Worker},
515        poolboy:checkout(Pool),
516        receive ok -> ok end
517    end),
518
519    Worker = receive {worker, Worker1} -> Worker1 end,
520    Ref = monitor(process, Worker),
521    exit(Worker, kill),
522    receive
523        {'DOWN', Ref, _, _, _} ->
524            ok
525    end,
526
527    ?assertEqual(1, length(get_monitors(Pool))),
528
529    Pid ! ok,
530    ok = pool_call(Pool, stop).
531
532get_monitors(Pid) ->
533    %% Synchronise with the Pid to ensure it has handled all expected work.
534    _ = sys:get_status(Pid),
535    [{monitors, Monitors}] = erlang:process_info(Pid, [monitors]),
536    Monitors.
537
538new_pool(Size, MaxOverflow) ->
539    poolboy:start_link([{name, {local, poolboy_test}},
540                        {worker_module, poolboy_test_worker},
541                        {size, Size}, {max_overflow, MaxOverflow}]).
542
543new_pool(Size, MaxOverflow, Strategy) ->
544    poolboy:start_link([{name, {local, poolboy_test}},
545                        {worker_module, poolboy_test_worker},
546                        {size, Size}, {max_overflow, MaxOverflow},
547                        {strategy, Strategy}]).
548
549pool_call(ServerRef, Request) ->
550    gen_server:call(ServerRef, Request).
551