pooler_tests.erl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. -module(pooler_tests).
  2. -include_lib("eunit/include/eunit.hrl").
  3. -compile([export_all]).
  4. % The `user' processes represent users of the pooler library. A user
  5. % process will take a pid, report details on the pid it has, release
  6. % and take a new pid, stop cleanly, and crash.
  7. start_user() ->
  8. spawn(fun() -> user_loop(start) end).
  9. user_id(Pid) ->
  10. Pid ! {get_tc_id, self()},
  11. receive
  12. {Type, Id} ->
  13. {Type, Id}
  14. end.
  15. user_new_tc(Pid) ->
  16. Pid ! new_tc.
  17. user_stop(Pid) ->
  18. Pid ! stop.
  19. user_crash(Pid) ->
  20. Pid ! crash.
  21. user_loop(Atom) when Atom =:= error_no_members orelse Atom =:= start ->
  22. user_loop(pooler:take_member(test_pool_1));
  23. user_loop(MyTC) ->
  24. receive
  25. {get_tc_id, From} ->
  26. From ! pooled_gs:get_id(MyTC),
  27. user_loop(MyTC);
  28. {ping_tc, From} ->
  29. From ! pooled_gs:ping(MyTC),
  30. user_loop(MyTC);
  31. {ping_count, From} ->
  32. From ! pooled_gs:ping_count(MyTC),
  33. user_loop(MyTC);
  34. new_tc ->
  35. pooler:return_member(test_pool_1, MyTC, ok),
  36. MyNewTC = pooler:take_member(test_pool_1),
  37. user_loop(MyNewTC);
  38. stop ->
  39. pooler:return_member(test_pool_1, MyTC, ok),
  40. stopped;
  41. crash ->
  42. erlang:error({user_loop, kaboom})
  43. end.
  44. % The `tc' processes represent the pids tracked by pooler for testing.
  45. % They have a type and an ID and can report their type and ID and
  46. % stop.
  47. tc_loop({Type, Id}) ->
  48. receive
  49. {get_id, From} ->
  50. From ! {ok, Type, Id},
  51. tc_loop({Type, Id});
  52. stop -> stopped;
  53. crash ->
  54. erlang:error({tc_loop, kaboom})
  55. end.
  56. get_tc_id(Pid) ->
  57. Pid ! {get_id, self()},
  58. receive
  59. {ok, Type, Id} ->
  60. {Type, Id}
  61. after 200 ->
  62. timeout
  63. end.
  64. stop_tc(Pid) ->
  65. Pid ! stop.
  66. tc_starter(Type) ->
  67. Ref = make_ref(),
  68. spawn_link(fun() -> tc_loop({Type, Ref}) end).
  69. assert_tc_valid(Pid) ->
  70. ?assertMatch({_Type, _Ref}, get_tc_id(Pid)),
  71. ok.
  72. % tc_sanity_test() ->
  73. % Pid1 = tc_starter("1"),
  74. % {"1", Id1} = get_tc_id(Pid1),
  75. % Pid2 = tc_starter("1"),
  76. % {"1", Id2} = get_tc_id(Pid2),
  77. % ?assertNot(Id1 == Id2),
  78. % stop_tc(Pid1),
  79. % stop_tc(Pid2).
  80. % user_sanity_test() ->
  81. % Pid1 = tc_starter("1"),
  82. % User = spawn(fun() -> user_loop(Pid1) end),
  83. % ?assertMatch({"1", _Ref}, user_id(User)),
  84. % user_crash(User),
  85. % stop_tc(Pid1).
  86. pooler_basics_test_() ->
  87. {setup,
  88. fun() ->
  89. application:set_env(pooler, metrics_module, fake_metrics),
  90. fake_metrics:start_link()
  91. end,
  92. fun(_X) ->
  93. fake_metrics:stop()
  94. end,
  95. {foreach,
  96. % setup
  97. fun() ->
  98. Pools = [[{name, test_pool_1},
  99. {max_count, 3},
  100. {init_count, 2},
  101. {start_mfa,
  102. {pooled_gs, start_link, [{"type-0"}]}}]],
  103. application:set_env(pooler, pools, Pools),
  104. %% error_logger:delete_report_handler(error_logger_tty_h),
  105. application:start(crypto),
  106. application:start(pooler)
  107. end,
  108. fun(_X) ->
  109. application:stop(pooler)
  110. end,
  111. [
  112. {"there are init_count members at start",
  113. fun() ->
  114. Stats = [ P || {P, {_, free, _}} <- pooler:pool_stats(test_pool_1) ],
  115. ?assertEqual(2, length(Stats))
  116. end},
  117. {"take and return one",
  118. fun() ->
  119. P = pooler:take_member(test_pool_1),
  120. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  121. ok = pooler:return_member(test_pool_1, P, ok)
  122. end},
  123. {"take and return one, named pool",
  124. fun() ->
  125. P = pooler:take_member(test_pool_1),
  126. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  127. ok, pooler:return_member(test_pool_1, P)
  128. end},
  129. {"attempt to take form unknown pool",
  130. fun() ->
  131. %% since pools are now servers, an unknown pool will timeout
  132. ?assertExit({noproc, _}, pooler:take_member(bad_pool_name))
  133. end},
  134. {"pids are created on demand until max",
  135. fun() ->
  136. Pids = [pooler:take_member(test_pool_1), pooler:take_member(test_pool_1), pooler:take_member(test_pool_1)],
  137. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  138. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  139. PRefs = [ R || {_T, R} <- [ pooled_gs:get_id(P) || P <- Pids ] ],
  140. % no duplicates
  141. ?assertEqual(length(PRefs), length(lists:usort(PRefs)))
  142. end
  143. },
  144. {"pids are reused most recent return first",
  145. fun() ->
  146. P1 = pooler:take_member(test_pool_1),
  147. P2 = pooler:take_member(test_pool_1),
  148. ?assertNot(P1 == P2),
  149. ok = pooler:return_member(test_pool_1, P1, ok),
  150. ok = pooler:return_member(test_pool_1, P2, ok),
  151. % pids are reused most recent first
  152. ?assertEqual(P2, pooler:take_member(test_pool_1)),
  153. ?assertEqual(P1, pooler:take_member(test_pool_1))
  154. end},
  155. {"if an in-use pid crashes it is replaced",
  156. fun() ->
  157. Pids0 = [pooler:take_member(test_pool_1), pooler:take_member(test_pool_1),
  158. pooler:take_member(test_pool_1)],
  159. Ids0 = [ pooled_gs:get_id(P) || P <- Pids0 ],
  160. % crash them all
  161. [ pooled_gs:crash(P) || P <- Pids0 ],
  162. Pids1 = get_n_pids(3, []),
  163. Ids1 = [ pooled_gs:get_id(P) || P <- Pids1 ],
  164. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  165. end
  166. },
  167. {"if a free pid crashes it is replaced",
  168. fun() ->
  169. FreePids = [ P || {P, {_, free, _}} <- pooler:pool_stats(test_pool_1) ],
  170. [ exit(P, kill) || P <- FreePids ],
  171. Pids1 = get_n_pids(3, []),
  172. ?assertEqual(3, length(Pids1))
  173. end},
  174. {"if a pid is returned with bad status it is replaced",
  175. fun() ->
  176. Pids0 = [pooler:take_member(test_pool_1), pooler:take_member(test_pool_1), pooler:take_member(test_pool_1)],
  177. Ids0 = [ pooled_gs:get_id(P) || P <- Pids0 ],
  178. % return them all marking as bad
  179. [ pooler:return_member(test_pool_1, P, fail) || P <- Pids0 ],
  180. Pids1 = get_n_pids(3, []),
  181. Ids1 = [ pooled_gs:get_id(P) || P <- Pids1 ],
  182. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  183. end
  184. },
  185. {"if a consumer crashes, pid is replaced",
  186. fun() ->
  187. Consumer = start_user(),
  188. StartId = user_id(Consumer),
  189. user_crash(Consumer),
  190. NewPid = hd(get_n_pids(1, [])),
  191. NewId = pooled_gs:get_id(NewPid),
  192. ?assertNot(NewId == StartId)
  193. end
  194. },
  195. {"it is ok to return an unknown pid",
  196. fun() ->
  197. Bogus1 = spawn(fun() -> ok end),
  198. Bogus2 = spawn(fun() -> ok end),
  199. ?assertEqual(ok, pooler:return_member(test_pool_1, Bogus1, ok)),
  200. ?assertEqual(ok, pooler:return_member(test_pool_1, Bogus2, fail))
  201. end
  202. },
  203. {"calling return_member on error_no_members is ignored",
  204. fun() ->
  205. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members)),
  206. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members, ok)),
  207. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members, fail))
  208. end
  209. },
  210. {"metrics have been called",
  211. fun() ->
  212. %% exercise the API to ensure we have certain keys reported as metrics
  213. fake_metrics:reset_metrics(),
  214. Pids = [ pooler:take_member(test_pool_1) || _I <- lists:seq(1, 10) ],
  215. [ pooler:return_member(test_pool_1, P) || P <- Pids ],
  216. catch pooler:take_member(bad_pool_name),
  217. %% kill and unused member
  218. exit(hd(Pids), kill),
  219. %% kill a used member
  220. KillMe = pooler:take_member(test_pool_1),
  221. exit(KillMe, kill),
  222. %% FIXME: We need to wait for pooler to process the
  223. %% exit message. This is ugly, will fix later.
  224. timer:sleep(200), % :(
  225. ExpectKeys = [<<"pooler.error_no_members_count">>,
  226. <<"pooler.events">>,
  227. <<"pooler.killed_free_count">>,
  228. <<"pooler.killed_in_use_count">>,
  229. <<"pooler.test_pool_1.free_count">>,
  230. <<"pooler.test_pool_1.in_use_count">>,
  231. <<"pooler.test_pool_1.take_rate">>],
  232. Metrics = fake_metrics:get_metrics(),
  233. GotKeys = lists:usort([ Name || {Name, _, _} <- Metrics ]),
  234. ?assertEqual(ExpectKeys, GotKeys)
  235. end}
  236. ]}}.
  237. pooler_limit_failed_adds_test_() ->
  238. %% verify that pooler crashes completely if too many failures are
  239. %% encountered while trying to add pids.
  240. {setup,
  241. fun() ->
  242. Pools = [[{name, test_pool_1},
  243. {max_count, 10},
  244. {init_count, 10},
  245. {start_mfa,
  246. {pooled_gs, start_link, [crash]}}]],
  247. application:set_env(pooler, pools, Pools)
  248. end,
  249. fun(_) ->
  250. application:stop(pooler)
  251. end,
  252. fun() ->
  253. application:start(pooler),
  254. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  255. ?assertEqual(error_no_members, pooler:take_member(test_pool_1))
  256. end}.
  257. pooler_scheduled_cull_test_() ->
  258. {setup,
  259. fun() ->
  260. application:set_env(pooler, metrics_module, fake_metrics),
  261. fake_metrics:start_link(),
  262. Pools = [[{name, test_pool_1},
  263. {max_count, 10},
  264. {init_count, 2},
  265. {start_mfa, {pooled_gs, start_link, [{"type-0"}]}},
  266. {cull_interval, {200, ms}}]],
  267. application:set_env(pooler, pools, Pools),
  268. %% error_logger:delete_report_handler(error_logger_tty_h),
  269. application:start(pooler)
  270. end,
  271. fun(_X) ->
  272. fake_metrics:stop(),
  273. application:stop(pooler)
  274. end,
  275. [{"excess members are culled repeatedly",
  276. fun() ->
  277. %% take all members
  278. Pids1 = [ pooler:take_member(test_pool_1) || _X <- lists:seq(1, 10) ],
  279. %% return all
  280. [ pooler:return_member(test_pool_1, P) || P <- Pids1 ],
  281. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  282. %% wait for longer than cull delay
  283. timer:sleep(250),
  284. ?assertEqual(2, length(pooler:pool_stats(test_pool_1))),
  285. %% repeat the test to verify that culling gets rescheduled.
  286. Pids2 = [ pooler:take_member(test_pool_1) || _X <- lists:seq(1, 10) ],
  287. %% return all
  288. [ pooler:return_member(test_pool_1, P) || P <- Pids2 ],
  289. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  290. %% wait for longer than cull delay
  291. timer:sleep(250),
  292. ?assertEqual(2, length(pooler:pool_stats(test_pool_1)))
  293. end
  294. },
  295. {"non-excess members are not culled",
  296. fun() ->
  297. [P1, P2] = [pooler:take_member(test_pool_1) || _X <- [1, 2] ],
  298. [pooler:return_member(test_pool_1, P) || P <- [P1, P2] ],
  299. ?assertEqual(2, length(pooler:pool_stats(test_pool_1))),
  300. timer:sleep(250),
  301. ?assertEqual(2, length(pooler:pool_stats(test_pool_1)))
  302. end
  303. },
  304. {"in-use members are not culled",
  305. fun() ->
  306. %% take all members
  307. Pids = [ pooler:take_member(test_pool_1) || _X <- lists:seq(1, 10) ],
  308. %% don't return any
  309. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  310. %% wait for longer than cull delay
  311. timer:sleep(250),
  312. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  313. [ pooler:return_member(test_pool_1, P) || P <- Pids ]
  314. end}
  315. ]}.
  316. random_message_test_() ->
  317. {setup,
  318. fun() ->
  319. Pools = [[{name, test_pool_1},
  320. {max_count, 2},
  321. {init_count, 1},
  322. {start_mfa,
  323. {pooled_gs, start_link, [{"type-0"}]}}]],
  324. application:set_env(pooler, pools, Pools),
  325. error_logger:delete_report_handler(error_logger_tty_h),
  326. application:start(pooler),
  327. %% now send some bogus messages
  328. %% do the call in a throw-away process to avoid timeout error
  329. spawn(fun() -> catch gen_server:call(test_pool_1, {unexpected_garbage_msg, 5}) end),
  330. gen_server:cast(test_pool_1, {unexpected_garbage_msg, 6}),
  331. whereis(test_pool_1) ! {unexpected_garbage_msg, 7},
  332. ok
  333. end,
  334. fun(_) ->
  335. application:stop(pooler)
  336. end,
  337. [
  338. fun() ->
  339. Pid = pooler:take_member(test_pool_1),
  340. {Type, _} = pooled_gs:get_id(Pid),
  341. ?assertEqual("type-0", Type)
  342. end
  343. ]}.
  344. pooler_integration_test_() ->
  345. {foreach,
  346. % setup
  347. fun() ->
  348. Pools = [[{name, test_pool_1},
  349. {max_count, 10},
  350. {init_count, 10},
  351. {start_mfa,
  352. {pooled_gs, start_link, [{"type-0"}]}}]],
  353. application:set_env(pooler, pools, Pools),
  354. error_logger:delete_report_handler(error_logger_tty_h),
  355. application:start(pooler),
  356. Users = [ start_user() || _X <- lists:seq(1, 10) ],
  357. Users
  358. end,
  359. % cleanup
  360. fun(Users) ->
  361. [ user_stop(U) || U <- Users ],
  362. application:stop(pooler)
  363. end,
  364. %
  365. [
  366. fun(Users) ->
  367. fun() ->
  368. % each user has a different tc ID
  369. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  370. ?assertEqual(lists:usort(TcIds), TcIds)
  371. end
  372. end
  373. ,
  374. fun(Users) ->
  375. fun() ->
  376. % users still unique after a renew cycle
  377. [ user_new_tc(UPid) || UPid <- Users ],
  378. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  379. ?assertEqual(lists:usort(TcIds), TcIds)
  380. end
  381. end
  382. ,
  383. fun(Users) ->
  384. fun() ->
  385. % all users crash, pids are replaced
  386. TcIds1 = lists:sort([ user_id(UPid) || UPid <- Users ]),
  387. [ user_crash(UPid) || UPid <- Users ],
  388. Seq = lists:seq(1, 5),
  389. Users2 = [ start_user() || _X <- Seq ],
  390. TcIds2 = lists:sort([ user_id(UPid) || UPid <- Users2 ]),
  391. Both =
  392. sets:to_list(sets:intersection([sets:from_list(TcIds1),
  393. sets:from_list(TcIds2)])),
  394. ?assertEqual([], Both)
  395. end
  396. end
  397. ]
  398. }.
  399. time_as_millis_test_() ->
  400. Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
  401. Ones = [{{1, min}, 60000},
  402. {{1, sec}, 1000},
  403. {{1, ms}, 1},
  404. {{1, mu}, 0}],
  405. Misc = [{{3000, mu}, 3}],
  406. Tests = Zeros ++ Ones ++ Misc,
  407. [ ?_assertEqual(E, pooler:time_as_millis(I)) || {I, E} <- Tests ].
  408. time_as_micros_test_() ->
  409. Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
  410. Ones = [{{1, min}, 60000000},
  411. {{1, sec}, 1000000},
  412. {{1, ms}, 1000},
  413. {{1, mu}, 1}],
  414. Misc = [{{3000, mu}, 3000}],
  415. Tests = Zeros ++ Ones ++ Misc,
  416. [ ?_assertEqual(E, pooler:time_as_micros(I)) || {I, E} <- Tests ].
  417. % testing crash recovery means race conditions when either pids
  418. % haven't yet crashed or pooler hasn't recovered. So this helper loops
  419. % forver until N pids are obtained, ignoring error_no_members.
  420. get_n_pids(0, Acc) ->
  421. Acc;
  422. get_n_pids(N, Acc) ->
  423. case pooler:take_member(test_pool_1) of
  424. error_no_members ->
  425. get_n_pids(N, Acc);
  426. Pid ->
  427. get_n_pids(N - 1, [Pid|Acc])
  428. end.