Merge pull request #9 from cloudant/time-unit-parameterization
Time unit parameterization
diff --git a/.travis.yml b/.travis.yml
index 99e6cb3..6a3648b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,6 @@
language: erlang
otp_release:
- - 17.5
- - 17.4
- - 17.1
- - 17.0
- - R16B03-1
- - R14B04
- - R14B02
+ - 21.2
+ - 20.3
+ - 19.3
diff --git a/src/ets_lru.app.src b/src/ets_lru.app.src
index 2573a0f..c81ce11 100644
--- a/src/ets_lru.app.src
+++ b/src/ets_lru.app.src
@@ -13,9 +13,6 @@
{application, ets_lru, [
{description, "ETS Base LRU Cache"},
{vsn, git},
- {modules, [
- ets_lru
- ]},
{registered, []},
{applications, [
kernel,
diff --git a/src/ets_lru.erl b/src/ets_lru.erl
index 7a366ba..0f6fdb2 100644
--- a/src/ets_lru.erl
+++ b/src/ets_lru.erl
@@ -12,7 +12,7 @@
-module(ets_lru).
-behaviour(gen_server).
--vsn(1).
+-vsn(2).
-export([
@@ -44,11 +44,16 @@
]).
+-define(DEFAULT_TIME_UNIT, millisecond).
+
+-type time_value() :: integer().
+-type strict_monotonic_time() :: {time_value(), integer()}.
+
-record(entry, {
- key,
- val,
- atime,
- ctime
+ key :: term(),
+ val :: term(),
+ atime :: strict_monotonic_time(),
+ ctime :: strict_monotonic_time()
}).
-record(st, {
@@ -56,9 +61,10 @@
atimes,
ctimes,
- max_objs,
- max_size,
- max_lifetime
+ max_objs :: non_neg_integer() | undefined,
+ max_size :: non_neg_integer() | undefined,
+ max_lifetime :: non_neg_integer() | undefined,
+ time_unit = ?DEFAULT_TIME_UNIT :: atom()
}).
@@ -164,7 +170,7 @@
{reply, Values, St, 0};
handle_call({insert, Key, Val}, _From, St) ->
- NewATime = erlang:now(),
+ NewATime = strict_monotonic_time(St#st.time_unit),
Pattern = #entry{key=Key, atime='$1', _='_'},
case ets:match(St#st.objects, Pattern) of
[[ATime]] ->
@@ -233,7 +239,7 @@
Pattern = #entry{key=Key, atime='$1', _='_'},
case ets:match(St#st.objects, Pattern) of
[[ATime]] ->
- NewATime = erlang:now(),
+ NewATime = strict_monotonic_time(St#st.time_unit),
Update = {#entry.atime, NewATime},
true = ets:update_element(St#st.objects, Key, Update),
true = ets:delete(St#st.atimes, ATime),
@@ -275,13 +281,12 @@
trim_lifetime(#st{max_lifetime=undefined}) ->
ok;
trim_lifetime(#st{max_lifetime=Max}=St) ->
- Now = os:timestamp(),
+ Now = erlang:monotonic_time(St#st.time_unit),
case ets:first(St#st.ctimes) of
'$end_of_table' ->
ok;
- CTime ->
- DiffInMilli = timer:now_diff(Now, CTime) div 1000,
- case DiffInMilli > Max of
+ CTime = {Time, _} ->
+ case Now - Time > Max of
true ->
[{CTime, Key}] = ets:lookup(St#st.ctimes, CTime),
Pattern = #entry{key=Key, atime='$1', _='_'},
@@ -317,10 +322,10 @@
case ets:first(St#st.ctimes) of
'$end_of_table' ->
infinity;
- CTime ->
- Now = os:timestamp(),
- DiffInMilli = timer:now_diff(Now, CTime) div 1000,
- erlang:max(St#st.max_lifetime - DiffInMilli, 0)
+ {Time, _} ->
+ Now = erlang:monotonic_time(St#st.time_unit),
+ TimeDiff = Now - Time,
+ erlang:max(St#st.max_lifetime - TimeDiff, 0)
end.
@@ -332,6 +337,8 @@
set_options(St#st{max_size=N}, Rest);
set_options(St, [{max_lifetime, N} | Rest]) when is_integer(N), N >= 0 ->
set_options(St#st{max_lifetime=N}, Rest);
+set_options(St, [{time_unit, T} | Rest]) when is_atom(T) ->
+ set_options(St#st{time_unit=T}, Rest);
set_options(_, [Opt | _]) ->
throw({invalid_option, Opt}).
@@ -350,3 +357,8 @@
table_name(Name, Ext) ->
list_to_atom(atom_to_list(Name) ++ Ext).
+
+
+-spec strict_monotonic_time(atom()) -> strict_monotonic_time().
+strict_monotonic_time(TimeUnit) ->
+ {erlang:monotonic_time(TimeUnit), erlang:unique_integer([monotonic])}.
diff --git a/test/ets_lru_test.erl b/test/ets_lru_test.erl
index 21d8e28..f54299a 100644
--- a/test/ets_lru_test.erl
+++ b/test/ets_lru_test.erl
@@ -1,6 +1,5 @@
-module(ets_lru_test).
--compile([export_all]).
-include_lib("eunit/include/eunit.hrl").
@@ -326,3 +325,16 @@
receive {'DOWN', Ref, process, LRU, Reason} -> Reason end;
stop_lru({error, _}) ->
ok.
+
+valid_parameterized_time_unit_test() ->
+ Opts = [{time_unit, microsecond}],
+ {ok, LRU} = ets_lru:start_link(lru_test, Opts),
+ ?assert(is_process_alive(LRU)),
+ ok = ets_lru:insert(LRU, foo, bar),
+ ?assertEqual({ok, bar}, ets_lru:lookup(LRU, foo)),
+ ?assertEqual(ok, ets_lru:stop(LRU)).
+
+invalid_parameterized_time_unit_test() ->
+ Opts = [{time_unit, invalid}],
+ {ok, LRU} = ets_lru:start_link(lru_test, Opts),
+ ?assertExit(_, ets_lru:insert(LRU, foo, bar)).