Current section

Files

Jump to
kraft src kraft_cache_ets.erl
Raw

src/kraft_cache_ets.erl

-module(kraft_cache_ets).
-behaviour(kraft_cache).
% Callbacks
-export([init/2]).
-export([get/2]).
-export([update/3]).
-ignore_xref([{?MODULE, clear, 2}]).
-export([clear/2]).
-export([clean_cache/1]).
%--- Types ---------------------------------------------------------------------
-type config() :: #{
memory => unit(memory()),
age => unit(time()),
clean_interval => unit(time())
}.
-export_type([config/0]).
-type unit(Unit) :: {Unit, pos_integer()} | infinity.
-type memory() ::
tb
| gb
| mb
| kb
| tib
| gib
| mib
| kib
| terabyte
| gigabyte
| megabyte
| kilobyte
| tebibyte
| gibibyte
| mebibyte
| kibibyte
| byte.
-type time() ::
week
| day
| hour
| minute
| erlang:time_unit().
-opaque ref() :: {ets:tab(), map()}.
-export_type([ref/0]).
%--- Callbacks -----------------------------------------------------------------
-spec init(kraft_cache:type(), config()) -> ref().
init(Type, Config) ->
State = #{
memory => convert_memory(maps:get(memory, Config, infinity)),
age => convert_time_unit(maps:get(age, Config, infinity))
},
{ets:new(Type, [public]), State}.
get({Table, _Config}, Keypath) ->
case ets:lookup(Table, Keypath) of
[] -> miss;
[{_Hash, _Time, Content}] -> {hit, Content}
end.
update({Table, _Config}, Keypath, Content) ->
ets:insert(Table, {Keypath, erlang:system_time(), Content}),
ok.
clear({Table, _Config}, Keypath) ->
clear(Table, Keypath, ets:first(Table)).
clean_cache({Table, Config}) ->
Memory = maps:get(memory, Config, infinity),
Age = maps:get(age, Config, infinity),
Time =
case Age of
infinity ->
infinity;
Age ->
NativeAge = convert_time_unit(Age),
erlang:system_time() - NativeAge
end,
Words =
case Memory of
infinity -> infinity;
Memory -> round(Memory / erlang:system_info(wordsize))
end,
Rest = clean_cache_age(Table, ets:first(Table), Time, []),
clean_cache_memory(Table, Words, Rest).
%--- Internal ------------------------------------------------------------------
clean_cache_age(_Table, '$end_of_table', _Age, Rest) ->
lists:sort(Rest);
clean_cache_age(Table, Key, Age, Rest) ->
Next = ets:next(Table, Key),
case ets:lookup_element(Table, Key, 2) of
TS when TS < Age, Age =/= infinity ->
ets:delete(Table, Key),
clean_cache_age(Table, Next, Age, Rest);
TS ->
clean_cache_age(Table, Next, Age, [{TS, Key} | Rest])
end.
clean_cache_memory(_Table, _Target, []) ->
ok;
clean_cache_memory(Table, Target, [{_TS, Key} | Oldest]) ->
case ets:info(Table, memory) of
Memory when Memory > Target ->
ets:delete(Table, Key),
clean_cache_memory(Table, Target, Oldest);
_Memory ->
ok
end.
clear(_Table, _Prefix, '$end_of_table') ->
ok;
clear(Table, Prefix, Key) ->
% next must be called before delete, otherwise it crashes
Next = ets:next(Table, Key),
case lists:prefix(Prefix, Key) of
true -> ets:delete(Table, Key);
false -> ok
end,
clear(Table, Prefix, Next).
convert_memory(infinity) -> infinity;
% No one will ever need more than a 1 T(i)B of cache :-)
convert_memory({tb, Size}) -> convert_memory({terabyte, Size});
convert_memory({gb, Size}) -> convert_memory({gigabyte, Size});
convert_memory({mb, Size}) -> convert_memory({megabyte, Size});
convert_memory({kb, Size}) -> convert_memory({kilobyte, Size});
convert_memory({tib, Size}) -> convert_memory({tebibyte, Size});
convert_memory({gib, Size}) -> convert_memory({gibibyte, Size});
convert_memory({mib, Size}) -> convert_memory({mebibyte, Size});
convert_memory({kib, Size}) -> convert_memory({kibibyte, Size});
convert_memory({terabyte, Size}) -> convert_memory({gigabyte, 1000 * Size});
convert_memory({gigabyte, Size}) -> convert_memory({megabyte, 1000 * Size});
convert_memory({megabyte, Size}) -> convert_memory({kilobyte, 1000 * Size});
convert_memory({kilobyte, Size}) -> convert_memory({byte, 1000 * Size});
convert_memory({tebibyte, Size}) -> convert_memory({gibibyte, 1024 * Size});
convert_memory({gibibyte, Size}) -> convert_memory({mebibyte, 1024 * Size});
convert_memory({mebibyte, Size}) -> convert_memory({kibibyte, 1024 * Size});
convert_memory({kibibyte, Size}) -> convert_memory({byte, 1024 * Size});
convert_memory({byte, Size}) -> round(Size / erlang:system_info(wordsize)).
convert_time_unit(infinity) -> infinity;
convert_time_unit({week, Time}) -> convert_time_unit({day, 7 * Time});
convert_time_unit({day, Time}) -> convert_time_unit({hour, 24 * Time});
convert_time_unit({hour, Time}) -> convert_time_unit({minute, 60 * Time});
convert_time_unit({minute, Time}) -> convert_time_unit({second, 60 * Time});
convert_time_unit({Unit, Time}) -> erlang:convert_time_unit(Time, Unit, native).