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