diff options
-rw-r--r-- | src/couch_expiring_cache/src/couch_expiring_cache_server.erl | 10 | ||||
-rw-r--r-- | src/couch_expiring_cache/test/couch_expiring_cache_tests.erl | 101 |
2 files changed, 77 insertions, 34 deletions
diff --git a/src/couch_expiring_cache/src/couch_expiring_cache_server.erl b/src/couch_expiring_cache/src/couch_expiring_cache_server.erl index eb74e6eb2..74c432e25 100644 --- a/src/couch_expiring_cache/src/couch_expiring_cache_server.erl +++ b/src/couch_expiring_cache/src/couch_expiring_cache_server.erl @@ -17,6 +17,7 @@ -callback start_link() -> {ok, pid()} | ignore | {error, term()}. -export([ + now_ts/0, start_link/2 ]). @@ -80,10 +81,10 @@ handle_info(remove_expired, St) -> largest_elapsed := LargestElapsed } = St, - NowTS = erlang:system_time(?TIME_UNIT), + NowTS = now_ts(), OldestTS = max(OldestTS0, couch_expiring_cache_fdb:clear_range_to(Name, NowTS, BatchSize)), - Elapsed = erlang:system_time(?TIME_UNIT) - NowTS, + Elapsed = now_ts() - NowTS, {noreply, St#{ timer_ref := schedule_remove_expired(Period, MaxJitter), @@ -108,6 +109,11 @@ code_change(_OldVsn, St, _Extra) -> {ok, St}. +now_ts() -> + {Mega, Sec, Micro} = os:timestamp(), + ((Mega * 1000000) + Sec) * 1000 + Micro div 1000. + + %% Private diff --git a/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl b/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl index 2e06fcc5a..0780b8847 100644 --- a/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl +++ b/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl @@ -18,16 +18,9 @@ -include_lib("couch_expiring_cache/include/couch_expiring_cache.hrl"). --define(CACHE_NAME, <<"test">>). +-define(CACHE_NAME, atom_to_binary(?MODULE, utf8)). - -start_link() -> - Opts = #{ - cache_name => ?CACHE_NAME, - period => 20, - max_jitter => 0 - }, - couch_expiring_cache_server:start_link(?MODULE, Opts). +-define(FOREVER, 576460752303423488). % max int 64 bit couch_expiring_cache_basic_test_() -> @@ -56,7 +49,11 @@ teardown_couch(Ctx) -> setup() -> - {ok, Pid} = start_link(), + Opts = #{ + cache_name => ?CACHE_NAME, + period => 10, + max_jitter => 0}, + {ok, Pid} = couch_expiring_cache_server:start_link(?MODULE, Opts), true = unlink(Pid), #{pid => Pid}. @@ -66,10 +63,18 @@ teardown(#{pid := Pid}) -> simple_lifecycle(_) -> - {timeout, 10, ?_test(begin - Now = erlang:system_time(?TIME_UNIT), - StaleTS = Now + 100, - ExpiresTS = Now + 200, + % The entire test is racing against FDB being faster than timeout seconds + {timeout, 20, ?_test(begin + Start = couch_expiring_cache_server:now_ts(), + % Race Alert! + % We're betting on FDB returning a lookup faster than these: + Stale = 500, + Expires = 1000, + Timeout = 5000, + Interval = 5, + + StaleTS = Start + Stale, + ExpiresTS = Start + Expires, Name = ?CACHE_NAME, Key = <<"key">>, Val = <<"val">>, @@ -77,34 +82,66 @@ simple_lifecycle(_) -> ?assertEqual(ok, couch_expiring_cache_fdb:clear_all(Name)), ?assertEqual(not_found, couch_expiring_cache:lookup(Name, Key)), ?assertEqual([], entries(Name)), - ?assertEqual(ok, - couch_expiring_cache:insert(Name, Key, Val, StaleTS, ExpiresTS)), - ?assertEqual({fresh, Val}, couch_expiring_cache:lookup(Name, Key)), - ok = wait_lookup(Name, Key, {stale, Val}), + ?assertEqual(ok, couch_expiring_cache:insert(Name, Key, Val, + StaleTS, ExpiresTS)), + ok = attempt_fresh_and_stale_lookups(Name, Key, Timeout, Interval), % Refresh the existing key with updated timestamps - ?assertEqual(ok, - couch_expiring_cache:insert(Name, Key, Val, - StaleTS + 100, ExpiresTS + 100)), - ?assertEqual({fresh, Val}, couch_expiring_cache:lookup(Name, Key)), + Refresh = couch_expiring_cache_server:now_ts(), + ?assertEqual(ok, couch_expiring_cache:insert(Name, Key, Val, + Refresh + Stale, Refresh + Expires)), + ok = attempt_fresh_and_stale_lookups(Name, Key, Timeout, Interval), ?assertEqual(1, length(entries(Name))), - ok = wait_lookup(Name, Key, {stale, Val}), - ok = wait_lookup(Name, Key, expired), - ok = wait_lookup(Name, Key, not_found), - ?assertEqual([], entries(Name)), - ?assertEqual(not_found, couch_expiring_cache:lookup(Name, Key)) + % These last 2 are also races, betting on FDB to be reasonably + % fast on the home stretch + ok = wait_lookup(Name, Key, expired, Timeout, Interval), + ok = wait_lookup(Name, Key, not_found, Timeout, Interval), + ?assertEqual([], entries(Name)) end)}. +% In this race we're betting on FDB to take less than `Stale` and then +% `Expired` milliseconds to respond +attempt_fresh_and_stale_lookups(Name, Key, Timeout, Interval) -> + case couch_expiring_cache:lookup(Name, Key) of + {fresh, Val} -> + % We won that race, let's bet on another! + ok = wait_lookup(Name, Key, {stale, Val}, Timeout, Interval); + _ -> + % Unlucky! But don't fail the test just yet... + ok + end. + + entries(Name) -> - FarFuture = erlang:system_time(?TIME_UNIT) * 2, - couch_expiring_cache_fdb:get_range_to(Name, FarFuture, _Limit=100). + couch_expiring_cache_fdb:get_range_to(Name, ?FOREVER, _Limit=100). -wait_lookup(Name, Key, Expect) -> - test_util:wait(fun() -> +% This lookup races against Timeout +wait_lookup(Name, Key, Expect, Timeout, Interval) -> + wait(fun() -> case couch_expiring_cache:lookup(Name, Key) of Expect -> ok; _ -> wait end - end, _Timeout = 1000, _PollingInterval = 10). + end, Timeout, Interval). + + +wait(Fun, Timeout, Delay) -> + Now = couch_expiring_cache_server:now_ts(), + wait(Fun, Timeout, Delay, Now, Now). + + +wait(_Fun, Timeout, _Delay, Started, Prev) when Prev - Started > Timeout -> + timeout; + +wait(Fun, Timeout, Delay, Started, _Prev) -> + case Fun() of + wait -> + % http://erlang.org/doc/man/timer.html#sleep-1 + ok = timer:sleep(Delay), % always millisecond + wait(Fun, Timeout, Delay, Started, + couch_expiring_cache_server:now_ts()); + Else -> + Else + end. |