pooler_tests.erl 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  1. -module(pooler_tests).
  2. -include_lib("eunit/include/eunit.hrl").
  3. -include("../src/pooler.hrl").
  4. -compile([export_all]).
  5. % The `user' processes represent users of the pooler library. A user
  6. % process will take a pid, report details on the pid it has, release
  7. % and take a new pid, stop cleanly, and crash.
  8. start_user() ->
  9. spawn(fun() -> user_loop(start) end).
  10. user_id(Pid) ->
  11. Pid ! {get_tc_id, self()},
  12. receive
  13. {Type, Id} ->
  14. {Type, Id}
  15. end.
  16. user_new_tc(Pid) ->
  17. Pid ! new_tc.
  18. user_stop(Pid) ->
  19. Pid ! stop.
  20. user_crash(Pid) ->
  21. Pid ! crash.
  22. user_loop(Atom) when Atom =:= error_no_members orelse Atom =:= start ->
  23. user_loop(pooler:take_member(test_pool_1));
  24. user_loop(MyTC) ->
  25. receive
  26. {get_tc_id, From} ->
  27. From ! pooled_gs:get_id(MyTC),
  28. user_loop(MyTC);
  29. {ping_tc, From} ->
  30. From ! pooled_gs:ping(MyTC),
  31. user_loop(MyTC);
  32. {ping_count, From} ->
  33. From ! pooled_gs:ping_count(MyTC),
  34. user_loop(MyTC);
  35. new_tc ->
  36. pooler:return_member(test_pool_1, MyTC, ok),
  37. MyNewTC = pooler:take_member(test_pool_1),
  38. user_loop(MyNewTC);
  39. stop ->
  40. pooler:return_member(test_pool_1, MyTC, ok),
  41. stopped;
  42. crash ->
  43. erlang:error({user_loop, kaboom})
  44. end.
  45. % The `tc' processes represent the pids tracked by pooler for testing.
  46. % They have a type and an ID and can report their type and ID and
  47. % stop.
  48. tc_loop({Type, Id}) ->
  49. receive
  50. {get_id, From} ->
  51. From ! {ok, Type, Id},
  52. tc_loop({Type, Id});
  53. stop -> stopped;
  54. crash ->
  55. erlang:error({tc_loop, kaboom})
  56. end.
  57. get_tc_id(Pid) ->
  58. Pid ! {get_id, self()},
  59. receive
  60. {ok, Type, Id} ->
  61. {Type, Id}
  62. after 200 ->
  63. timeout
  64. end.
  65. stop_tc(Pid) ->
  66. Pid ! stop.
  67. tc_starter(Type) ->
  68. Ref = make_ref(),
  69. spawn_link(fun() -> tc_loop({Type, Ref}) end).
  70. assert_tc_valid(Pid) ->
  71. ?assertMatch({_Type, _Ref}, get_tc_id(Pid)),
  72. ok.
  73. % tc_sanity_test() ->
  74. % Pid1 = tc_starter("1"),
  75. % {"1", Id1} = get_tc_id(Pid1),
  76. % Pid2 = tc_starter("1"),
  77. % {"1", Id2} = get_tc_id(Pid2),
  78. % ?assertNot(Id1 == Id2),
  79. % stop_tc(Pid1),
  80. % stop_tc(Pid2).
  81. % user_sanity_test() ->
  82. % Pid1 = tc_starter("1"),
  83. % User = spawn(fun() -> user_loop(Pid1) end),
  84. % ?assertMatch({"1", _Ref}, user_id(User)),
  85. % user_crash(User),
  86. % stop_tc(Pid1).
  87. pooler_basics_via_config_test_() ->
  88. {setup,
  89. fun() ->
  90. application:set_env(pooler, metrics_module, fake_metrics),
  91. fake_metrics:start_link()
  92. end,
  93. fun(_X) ->
  94. fake_metrics:stop()
  95. end,
  96. {foreach,
  97. % setup
  98. fun() ->
  99. Pools = [[{name, test_pool_1},
  100. {max_count, 3},
  101. {init_count, 2},
  102. {cull_interval, {0, min}},
  103. {start_mfa,
  104. {pooled_gs, start_link, [{"type-0"}]}}]],
  105. application:set_env(pooler, pools, Pools),
  106. error_logger:delete_report_handler(error_logger_tty_h),
  107. application:start(pooler)
  108. end,
  109. fun(_X) ->
  110. application:stop(pooler)
  111. end,
  112. basic_tests()}}.
  113. pooler_basics_dynamic_test_() ->
  114. {setup,
  115. fun() ->
  116. application:set_env(pooler, metrics_module, fake_metrics),
  117. fake_metrics:start_link()
  118. end,
  119. fun(_X) ->
  120. fake_metrics:stop()
  121. end,
  122. {foreach,
  123. % setup
  124. fun() ->
  125. Pool = [{name, test_pool_1},
  126. {max_count, 3},
  127. {init_count, 2},
  128. {start_mfa,
  129. {pooled_gs, start_link, [{"type-0"}]}}],
  130. application:unset_env(pooler, pools),
  131. error_logger:delete_report_handler(error_logger_tty_h),
  132. application:start(pooler),
  133. pooler:new_pool(Pool)
  134. end,
  135. fun(_X) ->
  136. application:stop(pooler)
  137. end,
  138. basic_tests()}}.
  139. pooler_basics_integration_to_other_supervisor_test_() ->
  140. {setup,
  141. fun() ->
  142. application:set_env(pooler, metrics_module, fake_metrics),
  143. fake_metrics:start_link()
  144. end,
  145. fun(_X) ->
  146. fake_metrics:stop()
  147. end,
  148. {foreach,
  149. % setup
  150. fun() ->
  151. Pool = [{name, test_pool_1},
  152. {max_count, 3},
  153. {init_count, 2},
  154. {start_mfa,
  155. {pooled_gs, start_link, [{"type-0"}]}}],
  156. application:unset_env(pooler, pools),
  157. error_logger:delete_report_handler(error_logger_tty_h),
  158. application:start(pooler),
  159. supervisor:start_link(fake_external_supervisor, Pool)
  160. end,
  161. fun({ok, SupPid}) ->
  162. exit(SupPid, normal),
  163. application:stop(pooler)
  164. end,
  165. basic_tests()}}.
  166. basic_tests() ->
  167. [
  168. {"there are init_count members at start",
  169. fun() ->
  170. Stats = [ P || {P, {_, free, _}} <- pooler:pool_stats(test_pool_1) ],
  171. ?assertEqual(2, length(Stats))
  172. end},
  173. {"take and return one",
  174. fun() ->
  175. P = pooler:take_member(test_pool_1),
  176. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  177. ok = pooler:return_member(test_pool_1, P, ok)
  178. end},
  179. {"take and return one, named pool",
  180. fun() ->
  181. P = pooler:take_member(test_pool_1),
  182. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  183. ok, pooler:return_member(test_pool_1, P)
  184. end},
  185. {"attempt to take form unknown pool",
  186. fun() ->
  187. %% since pools are now servers, an unknown pool will timeout
  188. ?assertExit({noproc, _}, pooler:take_member(bad_pool_name))
  189. end},
  190. {"members creation is triggered after pool exhaustion until max",
  191. fun() ->
  192. %% init count is 2
  193. Pids0 = [pooler:take_member(test_pool_1), pooler:take_member(test_pool_1)],
  194. %% since new member creation is async, can only assert
  195. %% that we will get a pid, but may not be first try.
  196. Pids = get_n_pids(1, Pids0),
  197. %% pool is at max now, requests should give error
  198. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  199. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  200. PRefs = [ R || {_T, R} <- [ pooled_gs:get_id(P) || P <- Pids ] ],
  201. % no duplicates
  202. ?assertEqual(length(PRefs), length(lists:usort(PRefs)))
  203. end
  204. },
  205. {"pids are reused most recent return first",
  206. fun() ->
  207. P1 = pooler:take_member(test_pool_1),
  208. P2 = pooler:take_member(test_pool_1),
  209. ?assertNot(P1 == P2),
  210. ok = pooler:return_member(test_pool_1, P1, ok),
  211. ok = pooler:return_member(test_pool_1, P2, ok),
  212. % pids are reused most recent first
  213. ?assertEqual(P2, pooler:take_member(test_pool_1)),
  214. ?assertEqual(P1, pooler:take_member(test_pool_1))
  215. end},
  216. {"if an in-use pid crashes it is replaced",
  217. fun() ->
  218. Pids0 = get_n_pids(3, []),
  219. Ids0 = [ pooled_gs:get_id(P) || P <- Pids0 ],
  220. % crash them all
  221. [ pooled_gs:crash(P) || P <- Pids0 ],
  222. Pids1 = get_n_pids(3, []),
  223. Ids1 = [ pooled_gs:get_id(P) || P <- Pids1 ],
  224. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  225. end
  226. },
  227. {"if a free pid crashes it is replaced",
  228. fun() ->
  229. FreePids = [ P || {P, {_, free, _}} <- pooler:pool_stats(test_pool_1) ],
  230. [ exit(P, kill) || P <- FreePids ],
  231. Pids1 = get_n_pids(3, []),
  232. ?assertEqual(3, length(Pids1))
  233. end},
  234. {"if a pid is returned with bad status it is replaced",
  235. fun() ->
  236. Pids0 = get_n_pids(3, []),
  237. Ids0 = [ pooled_gs:get_id(P) || P <- Pids0 ],
  238. % return them all marking as bad
  239. [ pooler:return_member(test_pool_1, P, fail) || P <- Pids0 ],
  240. Pids1 = get_n_pids(3, []),
  241. Ids1 = [ pooled_gs:get_id(P) || P <- Pids1 ],
  242. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  243. end
  244. },
  245. {"if a consumer crashes, pid is replaced",
  246. fun() ->
  247. Consumer = start_user(),
  248. StartId = user_id(Consumer),
  249. user_crash(Consumer),
  250. NewPid = hd(get_n_pids(1, [])),
  251. NewId = pooled_gs:get_id(NewPid),
  252. ?assertNot(NewId == StartId)
  253. end
  254. },
  255. {"it is ok to return an unknown pid",
  256. fun() ->
  257. Bogus1 = spawn(fun() -> ok end),
  258. Bogus2 = spawn(fun() -> ok end),
  259. ?assertEqual(ok, pooler:return_member(test_pool_1, Bogus1, ok)),
  260. ?assertEqual(ok, pooler:return_member(test_pool_1, Bogus2, fail))
  261. end
  262. },
  263. {"it is ok to return a pid more than once",
  264. fun() ->
  265. M = pooler:take_member(test_pool_1),
  266. [ pooler:return_member(test_pool_1, M)
  267. || _I <- lists:seq(1, 37) ],
  268. M1 = pooler:take_member(test_pool_1),
  269. M2 = pooler:take_member(test_pool_1),
  270. ?assert(M1 =/= M2),
  271. Pool1 = gen_server:call(test_pool_1, dump_pool),
  272. ?assertEqual(2, Pool1#pool.in_use_count),
  273. ?assertEqual(0, Pool1#pool.free_count),
  274. pooler:return_member(test_pool_1, M1),
  275. pooler:return_member(test_pool_1, M2),
  276. Pool2 = gen_server:call(test_pool_1, dump_pool),
  277. ?assertEqual(0, Pool2#pool.in_use_count),
  278. ?assertEqual(2, Pool2#pool.free_count),
  279. ok
  280. end},
  281. {"calling return_member on error_no_members is ignored",
  282. fun() ->
  283. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members)),
  284. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members, ok)),
  285. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members, fail))
  286. end
  287. },
  288. {"dynamic pool creation",
  289. fun() ->
  290. PoolSpec = [{name, dyn_pool_1},
  291. {max_count, 3},
  292. {init_count, 2},
  293. {start_mfa,
  294. {pooled_gs, start_link, [{"dyn-0"}]}}],
  295. {ok, SupPid1} = pooler:new_pool(PoolSpec),
  296. ?assert(is_pid(SupPid1)),
  297. M = pooler:take_member(dyn_pool_1),
  298. ?assertMatch({"dyn-0", _Id}, pooled_gs:get_id(M)),
  299. ?assertEqual(ok, pooler:rm_pool(dyn_pool_1)),
  300. ?assertExit({noproc, _}, pooler:take_member(dyn_pool_1)),
  301. %% verify pool of same name can be created after removal
  302. {ok, SupPid2} = pooler:new_pool(PoolSpec),
  303. ?assert(is_pid(SupPid2)),
  304. %% remove non-existing pool
  305. ?assertEqual(ok, pooler:rm_pool(dyn_pool_X)),
  306. ?assertEqual(ok, pooler:rm_pool(dyn_pool_1))
  307. end},
  308. {"metrics have been called (no timeout/queue)",
  309. fun() ->
  310. %% exercise the API to ensure we have certain keys reported as metrics
  311. fake_metrics:reset_metrics(),
  312. Pids = [ pooler:take_member(test_pool_1) || _I <- lists:seq(1, 10) ],
  313. [ pooler:return_member(test_pool_1, P) || P <- Pids ],
  314. catch pooler:take_member(bad_pool_name),
  315. %% kill and unused member
  316. exit(hd(Pids), kill),
  317. %% kill a used member
  318. KillMe = pooler:take_member(test_pool_1),
  319. exit(KillMe, kill),
  320. %% FIXME: We need to wait for pooler to process the
  321. %% exit message. This is ugly, will fix later.
  322. timer:sleep(200), % :(
  323. ExpectKeys = lists:sort([<<"pooler.test_pool_1.error_no_members_count">>,
  324. <<"pooler.test_pool_1.events">>,
  325. <<"pooler.test_pool_1.free_count">>,
  326. <<"pooler.test_pool_1.in_use_count">>,
  327. <<"pooler.test_pool_1.killed_free_count">>,
  328. <<"pooler.test_pool_1.killed_in_use_count">>,
  329. <<"pooler.test_pool_1.take_rate">>]),
  330. Metrics = fake_metrics:get_metrics(),
  331. GotKeys = lists:usort([ Name || {Name, _, _} <- Metrics ]),
  332. ?assertEqual(ExpectKeys, GotKeys)
  333. end},
  334. {"metrics have been called (with timeout/queue)",
  335. fun() ->
  336. %% exercise the API to ensure we have certain keys reported as metrics
  337. fake_metrics:reset_metrics(),
  338. %% pass a non-zero timeout here to exercise queueing
  339. Pids = [ pooler:take_member(test_pool_1, 1) || _I <- lists:seq(1, 10) ],
  340. [ pooler:return_member(test_pool_1, P) || P <- Pids ],
  341. catch pooler:take_member(bad_pool_name),
  342. %% kill and unused member
  343. exit(hd(Pids), kill),
  344. %% kill a used member
  345. KillMe = pooler:take_member(test_pool_1),
  346. exit(KillMe, kill),
  347. %% FIXME: We need to wait for pooler to process the
  348. %% exit message. This is ugly, will fix later.
  349. timer:sleep(200), % :(
  350. ExpectKeys = lists:sort([<<"pooler.test_pool_1.error_no_members_count">>,
  351. <<"pooler.test_pool_1.events">>,
  352. <<"pooler.test_pool_1.free_count">>,
  353. <<"pooler.test_pool_1.in_use_count">>,
  354. <<"pooler.test_pool_1.killed_free_count">>,
  355. <<"pooler.test_pool_1.killed_in_use_count">>,
  356. <<"pooler.test_pool_1.take_rate">>,
  357. <<"pooler.test_pool_1.queue_count">>]),
  358. Metrics = fake_metrics:get_metrics(),
  359. GotKeys = lists:usort([ Name || {Name, _, _} <- Metrics ]),
  360. ?assertEqual(ExpectKeys, GotKeys)
  361. end},
  362. {"accept bad member is handled",
  363. fun() ->
  364. Bad = spawn(fun() -> ok end),
  365. FakeStarter = spawn(fun() -> starter end),
  366. ?assertEqual(ok, pooler:accept_member(test_pool_1, {FakeStarter, Bad}))
  367. end}
  368. ].
  369. pooler_groups_test_() ->
  370. {setup,
  371. fun() ->
  372. application:set_env(pooler, metrics_module, fake_metrics),
  373. fake_metrics:start_link()
  374. end,
  375. fun(_X) ->
  376. fake_metrics:stop()
  377. end,
  378. {foreach,
  379. % setup
  380. fun() ->
  381. Pools = [[{name, test_pool_1},
  382. {group, group_1},
  383. {max_count, 3},
  384. {init_count, 2},
  385. {start_mfa,
  386. {pooled_gs, start_link, [{"type-1-1"}]}}],
  387. [{name, test_pool_2},
  388. {group, group_1},
  389. {max_count, 3},
  390. {init_count, 2},
  391. {start_mfa,
  392. {pooled_gs, start_link, [{"type-1-2"}]}}],
  393. %% test_pool_3 not part of the group
  394. [{name, test_pool_3},
  395. {group, undefined},
  396. {max_count, 3},
  397. {init_count, 2},
  398. {start_mfa,
  399. {pooled_gs, start_link, [{"type-3"}]}}]
  400. ],
  401. application:set_env(pooler, pools, Pools),
  402. %% error_logger:delete_report_handler(error_logger_tty_h),
  403. pg2:start(),
  404. application:start(pooler)
  405. end,
  406. fun(_X) ->
  407. application:stop(pooler),
  408. application:stop(pg2)
  409. end,
  410. [
  411. {"take and return one group member (repeated)",
  412. fun() ->
  413. Types = [ begin
  414. Pid = pooler:take_group_member(group_1),
  415. {Type, _} = pooled_gs:get_id(Pid),
  416. ?assertMatch("type-1" ++ _, Type),
  417. ok = pooler:return_group_member(group_1, Pid, ok),
  418. Type
  419. end
  420. || _I <- lists:seq(1, 50) ],
  421. Type_1_1 = [ X || "type-1-1" = X <- Types ],
  422. Type_1_2 = [ X || "type-1-2" = X <- Types ],
  423. ?assert(length(Type_1_1) > 0),
  424. ?assert(length(Type_1_2) > 0)
  425. end},
  426. {"take member from unknown group",
  427. fun() ->
  428. ?assertEqual({error_no_group, not_a_group},
  429. pooler:take_group_member(not_a_group))
  430. end},
  431. {"return member to unknown group",
  432. fun() ->
  433. Pid = pooler:take_group_member(group_1),
  434. ?assertEqual(ok, pooler:return_group_member(no_such_group, Pid))
  435. end},
  436. {"return member to wrong group",
  437. fun() ->
  438. Pid = pooler:take_member(test_pool_3),
  439. ?assertEqual(ok, pooler:return_group_member(group_1, Pid))
  440. end},
  441. {"take member from empty group",
  442. fun() ->
  443. %% artificially empty group member list
  444. [ pg2:leave(group_1, M) || M <- pg2:get_members(group_1) ],
  445. ?assertEqual(error_no_members, pooler:take_group_member(group_1))
  446. end},
  447. {"return member to group, implied ok",
  448. fun() ->
  449. Pid = pooler:take_group_member(group_1),
  450. ?assertEqual(ok, pooler:return_group_member(group_1, Pid))
  451. end},
  452. {"return error_no_member to group",
  453. fun() ->
  454. ?assertEqual(ok, pooler:return_group_member(group_1, error_no_members))
  455. end},
  456. {"exhaust pools in group",
  457. fun() ->
  458. Pids = get_n_pids_group(group_1, 6, []),
  459. %% they should all be pids
  460. [ begin
  461. {Type, _} = pooled_gs:get_id(P),
  462. ?assertMatch("type-1" ++ _, Type),
  463. ok
  464. end || P <- Pids ],
  465. %% further attempts should be error
  466. [error_no_members,
  467. error_no_members,
  468. error_no_members] = [ pooler:take_group_member(group_1)
  469. || _I <- lists:seq(1, 3) ]
  470. end},
  471. {"rm_group with nonexisting group",
  472. fun() ->
  473. ?assertEqual(ok, pooler:rm_group(i_dont_exist))
  474. end},
  475. {"rm_group with existing empty group",
  476. fun() ->
  477. ?assertEqual(ok, pooler:rm_pool(test_pool_1)),
  478. ?assertEqual(ok, pooler:rm_pool(test_pool_2)),
  479. ?assertEqual(error_no_members, pooler:take_group_member(group_1)),
  480. ?assertEqual(ok, pooler:rm_group(group_1)),
  481. ?assertExit({noproc, _}, pooler:take_member(test_pool_1)),
  482. ?assertExit({noproc, _}, pooler:take_member(test_pool_2)),
  483. ?assertEqual({error_no_group, group_1},
  484. pooler:take_group_member(group_1))
  485. end},
  486. {"rm_group with existing non-empty group",
  487. fun() ->
  488. %% Verify that group members exist
  489. MemberPid = pooler:take_group_member(group_1),
  490. ?assert(is_pid(MemberPid)),
  491. pooler:return_group_member(group_1, MemberPid),
  492. Pool1Pid = pooler:take_member(test_pool_1),
  493. ?assert(is_pid(Pool1Pid)),
  494. pooler:return_member(test_pool_1, Pool1Pid),
  495. Pool2Pid = pooler:take_member(test_pool_2),
  496. ?assert(is_pid(Pool2Pid)),
  497. pooler:return_member(test_pool_2, Pool2Pid),
  498. %% Delete and verify that group and pools are destroyed
  499. ?assertEqual(ok, pooler:rm_group(group_1)),
  500. ?assertExit({noproc, _}, pooler:take_member(test_pool_1)),
  501. ?assertExit({noproc, _}, pooler:take_member(test_pool_2)),
  502. ?assertEqual({error_no_group, group_1},
  503. pooler:take_group_member(group_1))
  504. end}
  505. ]}}.
  506. pooler_limit_failed_adds_test_() ->
  507. %% verify that pooler crashes completely if too many failures are
  508. %% encountered while trying to add pids.
  509. {setup,
  510. fun() ->
  511. Pools = [[{name, test_pool_1},
  512. {max_count, 10},
  513. {init_count, 10},
  514. {start_mfa,
  515. {pooled_gs, start_link, [crash]}}]],
  516. application:set_env(pooler, pools, Pools)
  517. end,
  518. fun(_) ->
  519. application:stop(pooler)
  520. end,
  521. fun() ->
  522. application:start(pooler),
  523. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  524. ?assertEqual(error_no_members, pooler:take_member(test_pool_1))
  525. end}.
  526. pooler_scheduled_cull_test_() ->
  527. {setup,
  528. fun() ->
  529. application:set_env(pooler, metrics_module, fake_metrics),
  530. fake_metrics:start_link(),
  531. Pools = [[{name, test_pool_1},
  532. {max_count, 10},
  533. {init_count, 2},
  534. {start_mfa, {pooled_gs, start_link, [{"type-0"}]}},
  535. {cull_interval, {200, ms}},
  536. {max_age, {0, min}}]],
  537. application:set_env(pooler, pools, Pools),
  538. %% error_logger:delete_report_handler(error_logger_tty_h),
  539. application:start(pooler)
  540. end,
  541. fun(_X) ->
  542. fake_metrics:stop(),
  543. application:stop(pooler)
  544. end,
  545. [
  546. {foreach,
  547. fun() ->
  548. Pids = get_n_pids(test_pool_1, 10, []),
  549. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  550. ?assertEqual(10, length(Pids)),
  551. Pids
  552. end,
  553. fun(Pids) ->
  554. [ pooler:return_member(test_pool_1, P) || P <- Pids ]
  555. end,
  556. [
  557. fun(Pids) ->
  558. {"excess members are culled run 1",
  559. fun() ->
  560. [ pooler:return_member(test_pool_1, P) || P <- Pids ],
  561. %% wait for longer than cull delay
  562. timer:sleep(250),
  563. ?assertEqual(2, length(pooler:pool_stats(test_pool_1)))
  564. end}
  565. end,
  566. fun(Pids) ->
  567. {"excess members are culled run 2",
  568. fun() ->
  569. [ pooler:return_member(test_pool_1, P) || P <- Pids ],
  570. %% wait for longer than cull delay
  571. timer:sleep(250),
  572. ?assertEqual(2, length(pooler:pool_stats(test_pool_1)))
  573. end}
  574. end,
  575. fun(Pids) -> in_use_members_not_culled(Pids, 1) end,
  576. fun(Pids) -> in_use_members_not_culled(Pids, 2) end,
  577. fun(Pids) -> in_use_members_not_culled(Pids, 3) end,
  578. fun(Pids) -> in_use_members_not_culled(Pids, 4) end,
  579. fun(Pids) -> in_use_members_not_culled(Pids, 5) end,
  580. fun(Pids) -> in_use_members_not_culled(Pids, 6) end
  581. ]},
  582. {"no cull when init_count matches max_count",
  583. %% not sure how to verify this. But this test at least
  584. %% exercises the code path.
  585. fun() ->
  586. Config = [{name, test_static_pool_1},
  587. {max_count, 2},
  588. {init_count, 2},
  589. {start_mfa, {pooled_gs, start_link, [{"static-0"}]}},
  590. {cull_interval, {200, ms}}], % ignored
  591. pooler:new_pool(Config),
  592. P = pooler:take_member(test_static_pool_1),
  593. ?assertMatch({"static-0", _}, pooled_gs:get_id(P)),
  594. pooler:return_member(test_static_pool_1, P),
  595. ok
  596. end}
  597. ]}.
  598. in_use_members_not_culled(Pids, N) ->
  599. {"in-use members are not culled " ++ erlang:integer_to_list(N),
  600. fun() ->
  601. %% wait for longer than cull delay
  602. timer:sleep(250),
  603. PidCount = length(Pids),
  604. ?assertEqual(PidCount,
  605. length(pooler:pool_stats(test_pool_1))),
  606. Returns = lists:sublist(Pids, N),
  607. [ pooler:return_member(test_pool_1, P)
  608. || P <- Returns ],
  609. timer:sleep(250),
  610. ?assertEqual(PidCount - N,
  611. length(pooler:pool_stats(test_pool_1)))
  612. end}.
  613. random_message_test_() ->
  614. {setup,
  615. fun() ->
  616. Pools = [[{name, test_pool_1},
  617. {max_count, 2},
  618. {init_count, 1},
  619. {start_mfa,
  620. {pooled_gs, start_link, [{"type-0"}]}}]],
  621. application:set_env(pooler, pools, Pools),
  622. error_logger:delete_report_handler(error_logger_tty_h),
  623. application:start(pooler),
  624. %% now send some bogus messages
  625. %% do the call in a throw-away process to avoid timeout error
  626. spawn(fun() -> catch gen_server:call(test_pool_1, {unexpected_garbage_msg, 5}) end),
  627. gen_server:cast(test_pool_1, {unexpected_garbage_msg, 6}),
  628. whereis(test_pool_1) ! {unexpected_garbage_msg, 7},
  629. ok
  630. end,
  631. fun(_) ->
  632. application:stop(pooler)
  633. end,
  634. [
  635. fun() ->
  636. Pid = spawn(fun() -> ok end),
  637. MonMsg = {'DOWN', erlang:make_ref(), process, Pid, because},
  638. test_pool_1 ! MonMsg
  639. end,
  640. fun() ->
  641. Pid = pooler:take_member(test_pool_1),
  642. {Type, _} = pooled_gs:get_id(Pid),
  643. ?assertEqual("type-0", Type)
  644. end,
  645. fun() ->
  646. RawPool = gen_server:call(test_pool_1, dump_pool),
  647. ?assertEqual(pool, element(1, RawPool))
  648. end
  649. ]}.
  650. pooler_integration_long_init_test_() ->
  651. {foreach,
  652. % setup
  653. fun() ->
  654. Pool = [{name, test_pool_1},
  655. {max_count, 10},
  656. {init_count, 0},
  657. {member_start_timeout, {10, ms}},
  658. {start_mfa,
  659. {pooled_gs, start_link, [{"type-0", fun() -> timer:sleep(15) end}]}}],
  660. application:set_env(pooler, pools, [Pool]),
  661. application:start(pooler)
  662. end,
  663. % cleanup
  664. fun(_) ->
  665. application:stop(pooler)
  666. end,
  667. %
  668. [
  669. fun(_) ->
  670. % Test what happens when pool members take too long to start.
  671. % The pooler_starter should kill off stale members, there by
  672. % reducing the number of children of the member_sup. This
  673. % activity occurs both during take member and accept member.
  674. % Accordingly, the count should go to zero once all starters
  675. % check in.
  676. fun() ->
  677. ?assertEqual(0, children_count(pooler_test_pool_1_member_sup)),
  678. [begin
  679. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  680. ?assertEqual(1, starting_members(test_pool_1))
  681. end
  682. || _ <- lists:seq(1,10)],
  683. ?assertEqual(10, children_count(pooler_test_pool_1_member_sup)),
  684. timer:sleep(150),
  685. ?assertEqual(0, children_count(pooler_test_pool_1_member_sup)),
  686. ?assertEqual(0, starting_members(test_pool_1))
  687. end
  688. end
  689. ]
  690. }.
  691. sleep_for_configured_timeout() ->
  692. SleepTime = case application:get_env(pooler, sleep_time) of
  693. {ok, Val} ->
  694. Val;
  695. _ ->
  696. 0
  697. end,
  698. timer:sleep(SleepTime).
  699. pooler_integration_queueing_test_() ->
  700. {foreach,
  701. % setup
  702. fun() ->
  703. Pool = [{name, test_pool_1},
  704. {max_count, 10},
  705. {queue_max, 10},
  706. {init_count, 0},
  707. {metrics, fake_metrics},
  708. {member_start_timeout, {5, sec}},
  709. {start_mfa,
  710. {pooled_gs, start_link, [
  711. {"type-0",
  712. fun pooler_tests:sleep_for_configured_timeout/0 }
  713. ]
  714. }
  715. }
  716. ],
  717. application:set_env(pooler, pools, [Pool]),
  718. fake_metrics:start_link(),
  719. application:start(pooler)
  720. end,
  721. % cleanup
  722. fun(_) ->
  723. fake_metrics:stop(),
  724. application:stop(pooler)
  725. end,
  726. [
  727. fun(_) ->
  728. fun() ->
  729. ?assertEqual(0, (dump_pool(test_pool_1))#pool.free_count),
  730. Val = pooler:take_member(test_pool_1, 10),
  731. ?assert(is_pid(Val)),
  732. pooler:return_member(test_pool_1, Val)
  733. end
  734. end,
  735. fun(_) ->
  736. fun() ->
  737. application:set_env(pooler, sleep_time, 1),
  738. ?assertEqual(0, (dump_pool(test_pool_1))#pool.free_count),
  739. Val = pooler:take_member(test_pool_1, 0),
  740. ?assertEqual(error_no_members, Val),
  741. timer:sleep(50),
  742. %Next request should be available
  743. Pid = pooler:take_member(test_pool_1, 0),
  744. ?assert(is_pid(Pid)),
  745. pooler:return_member(test_pool_1, Pid)
  746. end
  747. end,
  748. fun(_) ->
  749. fun() ->
  750. application:set_env(pooler, sleep_time, 10),
  751. ?assertEqual(0, (dump_pool(test_pool_1))#pool.free_count),
  752. [
  753. ?assertEqual(pooler:take_member(test_pool_1, 0), error_no_members) ||
  754. _ <- lists:seq(1, (dump_pool(test_pool_1))#pool.max_count)],
  755. timer:sleep(50),
  756. %Next request should be available
  757. Pid = pooler:take_member(test_pool_1, 0),
  758. ?assert(is_pid(Pid)),
  759. pooler:return_member(test_pool_1, Pid)
  760. end
  761. end,
  762. fun(_) ->
  763. fun() ->
  764. % fill to queue_max, next request should return immediately with no_members
  765. % Will return a if queue max is not enforced.
  766. application:set_env(pooler, sleep_time, 100),
  767. [ proc_lib:spawn(fun() ->
  768. Val = pooler:take_member(test_pool_1, 200),
  769. ?assert(is_pid(Val)),
  770. pooler:return_member(Val)
  771. end)
  772. || _ <- lists:seq(1, (dump_pool(test_pool_1))#pool.max_count)
  773. ],
  774. timer:sleep(50),
  775. ?assertEqual(10, queue:len((dump_pool(test_pool_1))#pool.queued_requestors)),
  776. ?assertEqual(pooler:take_member(test_pool_1, 500), error_no_members),
  777. ExpectKeys = lists:sort([<<"pooler.test_pool_1.error_no_members_count">>,
  778. <<"pooler.test_pool_1.events">>,
  779. <<"pooler.test_pool_1.take_rate">>,
  780. <<"pooler.test_pool_1.queue_count">>,
  781. <<"pooler.test_pool_1.queue_max_reached">>]),
  782. Metrics = fake_metrics:get_metrics(),
  783. GotKeys = lists:usort([ Name || {Name, _, _} <- Metrics ]),
  784. ?assertEqual(ExpectKeys, GotKeys),
  785. timer:sleep(100),
  786. Val = pooler:take_member(test_pool_1, 500),
  787. ?assert(is_pid(Val)),
  788. pooler:return_member(test_pool_1, Val)
  789. end
  790. end
  791. ]
  792. }.
  793. pooler_integration_queueing_return_member_test_() ->
  794. {foreach,
  795. % setup
  796. fun() ->
  797. Pool = [{name, test_pool_1},
  798. {max_count, 10},
  799. {queue_max, 10},
  800. {init_count, 10},
  801. {metrics, fake_metrics},
  802. {member_start_timeout, {5, sec}},
  803. {start_mfa,
  804. {pooled_gs, start_link, [
  805. {"type-0",
  806. fun pooler_tests:sleep_for_configured_timeout/0 }
  807. ]
  808. }
  809. }
  810. ],
  811. application:set_env(pooler, pools, [Pool]),
  812. fake_metrics:start_link(),
  813. application:start(pooler)
  814. end,
  815. % cleanup
  816. fun(_) ->
  817. fake_metrics:stop(),
  818. application:stop(pooler)
  819. end,
  820. [
  821. fun(_) ->
  822. fun() ->
  823. application:set_env(pooler, sleep_time, 0),
  824. Pids = [ proc_lib:spawn_link(fun() ->
  825. Val = pooler:take_member(test_pool_1, 200),
  826. ?assert(is_pid(Val)),
  827. receive
  828. _ ->
  829. pooler:return_member(test_pool_1, Val)
  830. after
  831. 5000 ->
  832. pooler:return_member(test_pool_1, Val)
  833. end
  834. end)
  835. || _ <- lists:seq(1, (dump_pool(test_pool_1))#pool.max_count)
  836. ],
  837. timer:sleep(1),
  838. Parent = self(),
  839. proc_lib:spawn_link(fun() ->
  840. Val = pooler:take_member(test_pool_1, 200),
  841. Parent ! Val
  842. end),
  843. [Pid ! return || Pid <- Pids],
  844. receive
  845. Result ->
  846. ?assert(is_pid(Result)),
  847. pooler:return_member(test_pool_1, Result)
  848. end,
  849. ?assertEqual((dump_pool(test_pool_1))#pool.max_count, length((dump_pool(test_pool_1))#pool.free_pids)),
  850. ?assertEqual((dump_pool(test_pool_1))#pool.max_count, (dump_pool(test_pool_1))#pool.free_count)
  851. end
  852. end
  853. ]
  854. }.
  855. pooler_integration_test_() ->
  856. {foreach,
  857. % setup
  858. fun() ->
  859. Pools = [[{name, test_pool_1},
  860. {max_count, 10},
  861. {init_count, 10},
  862. {start_mfa,
  863. {pooled_gs, start_link, [{"type-0"}]}}]],
  864. application:set_env(pooler, pools, Pools),
  865. error_logger:delete_report_handler(error_logger_tty_h),
  866. application:start(pooler),
  867. Users = [ start_user() || _X <- lists:seq(1, 10) ],
  868. Users
  869. end,
  870. % cleanup
  871. fun(Users) ->
  872. [ user_stop(U) || U <- Users ],
  873. application:stop(pooler)
  874. end,
  875. %
  876. [
  877. fun(Users) ->
  878. fun() ->
  879. % each user has a different tc ID
  880. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  881. ?assertEqual(lists:usort(TcIds), TcIds)
  882. end
  883. end
  884. ,
  885. fun(Users) ->
  886. fun() ->
  887. % users still unique after a renew cycle
  888. [ user_new_tc(UPid) || UPid <- Users ],
  889. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  890. ?assertEqual(lists:usort(TcIds), TcIds)
  891. end
  892. end
  893. ,
  894. fun(Users) ->
  895. fun() ->
  896. % all users crash, pids are replaced
  897. TcIds1 = lists:sort([ user_id(UPid) || UPid <- Users ]),
  898. [ user_crash(UPid) || UPid <- Users ],
  899. Seq = lists:seq(1, 5),
  900. Users2 = [ start_user() || _X <- Seq ],
  901. TcIds2 = lists:sort([ user_id(UPid) || UPid <- Users2 ]),
  902. Both =
  903. sets:to_list(sets:intersection([sets:from_list(TcIds1),
  904. sets:from_list(TcIds2)])),
  905. ?assertEqual([], Both)
  906. end
  907. end
  908. ]
  909. }.
  910. pooler_auto_grow_disabled_by_default_test_() ->
  911. {setup,
  912. fun() ->
  913. application:set_env(pooler, metrics_module, fake_metrics),
  914. fake_metrics:start_link()
  915. end,
  916. fun(_X) ->
  917. fake_metrics:stop()
  918. end,
  919. {foreach,
  920. % setup
  921. fun() ->
  922. Pool = [{name, test_pool_1},
  923. {max_count, 5},
  924. {init_count, 2},
  925. {start_mfa,
  926. {pooled_gs, start_link, [{"type-0"}]}}],
  927. application:unset_env(pooler, pools),
  928. error_logger:delete_report_handler(error_logger_tty_h),
  929. application:start(pooler),
  930. pooler:new_pool(Pool)
  931. end,
  932. fun(_X) ->
  933. application:stop(pooler)
  934. end,
  935. [
  936. {"take one, and it should not auto-grow",
  937. fun() ->
  938. ?assertEqual(2, (dump_pool(test_pool_1))#pool.free_count),
  939. P = pooler:take_member(test_pool_1),
  940. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  941. timer:sleep(100),
  942. ?assertEqual(1, (dump_pool(test_pool_1))#pool.free_count),
  943. ok, pooler:return_member(test_pool_1, P)
  944. end}
  945. ]}}.
  946. pooler_auto_grow_enabled_test_() ->
  947. {setup,
  948. fun() ->
  949. application:set_env(pooler, metrics_module, fake_metrics),
  950. fake_metrics:start_link()
  951. end,
  952. fun(_X) ->
  953. fake_metrics:stop()
  954. end,
  955. {foreach,
  956. % setup
  957. fun() ->
  958. Pool = [{name, test_pool_1},
  959. {max_count, 5},
  960. {init_count, 2},
  961. {auto_grow_threshold, 1},
  962. {start_mfa,
  963. {pooled_gs, start_link, [{"type-0"}]}}],
  964. application:unset_env(pooler, pools),
  965. error_logger:delete_report_handler(error_logger_tty_h),
  966. application:start(pooler),
  967. pooler:new_pool(Pool)
  968. end,
  969. fun(_X) ->
  970. application:stop(pooler)
  971. end,
  972. [
  973. {"take one, and it should grow by 2",
  974. fun() ->
  975. ?assertEqual(2, (dump_pool(test_pool_1))#pool.free_count),
  976. P = pooler:take_member(test_pool_1),
  977. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  978. timer:sleep(100),
  979. ?assertEqual(3, (dump_pool(test_pool_1))#pool.free_count),
  980. ok, pooler:return_member(test_pool_1, P)
  981. end}
  982. ]}}.
  983. time_as_millis_test_() ->
  984. Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
  985. Ones = [{{1, min}, 60000},
  986. {{1, sec}, 1000},
  987. {{1, ms}, 1},
  988. {{1, mu}, 0}],
  989. Misc = [{{3000, mu}, 3}],
  990. Tests = Zeros ++ Ones ++ Misc,
  991. [ ?_assertEqual(E, pooler:time_as_millis(I)) || {I, E} <- Tests ].
  992. time_as_micros_test_() ->
  993. Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
  994. Ones = [{{1, min}, 60000000},
  995. {{1, sec}, 1000000},
  996. {{1, ms}, 1000},
  997. {{1, mu}, 1}],
  998. Misc = [{{3000, mu}, 3000}],
  999. Tests = Zeros ++ Ones ++ Misc,
  1000. [ ?_assertEqual(E, pooler:time_as_micros(I)) || {I, E} <- Tests ].
  1001. % testing crash recovery means race conditions when either pids
  1002. % haven't yet crashed or pooler hasn't recovered. So this helper loops
  1003. % forver until N pids are obtained, ignoring error_no_members.
  1004. get_n_pids(N, Acc) ->
  1005. get_n_pids(test_pool_1, N, Acc).
  1006. get_n_pids(_Pool, 0, Acc) ->
  1007. Acc;
  1008. get_n_pids(Pool, N, Acc) ->
  1009. case pooler:take_member(Pool) of
  1010. error_no_members ->
  1011. get_n_pids(Pool, N, Acc);
  1012. Pid ->
  1013. get_n_pids(Pool, N - 1, [Pid|Acc])
  1014. end.
  1015. get_n_pids_group(_Group, 0, Acc) ->
  1016. Acc;
  1017. get_n_pids_group(Group, N, Acc) ->
  1018. case pooler:take_group_member(Group) of
  1019. error_no_members ->
  1020. get_n_pids_group(Group, N, Acc);
  1021. Pid ->
  1022. get_n_pids_group(Group, N - 1, [Pid|Acc])
  1023. end.
  1024. children_count(SupId) ->
  1025. length(supervisor:which_children(SupId)).
  1026. starting_members(PoolName) ->
  1027. length((dump_pool(PoolName))#pool.starting_members).
  1028. dump_pool(PoolName) ->
  1029. gen_server:call(PoolName, dump_pool).