Commit 22fd5c8e authored by benoît chesneau's avatar benoît chesneau

allows to lazily update a value using a function

This changes allows to pass a function to the add and contains_or_add functions so you can fetch the value only when needed.
parent 1de52a21
Pipeline #4572921 passed with stage
in 3 minutes and 48 seconds
......@@ -103,9 +103,14 @@ stop(Cache) ->
end.
%% @doc adds a value to the cache. Returns true if an eviction occured.
-spec add(cache(), term(), term()) -> true | false.
-spec add(cache(), term(), term() | fun()) -> true | false.
add(Cache, Key, Value) ->
call(Cache, {add, Key, Value}).
Reply = call(Cache, {add, Key, Value}),
case Reply of
{error, Error} -> erlang:error(Error);
_ -> Reply
end.
%% @doc lookup a key's value from the cache. Return undefined if it's not
%% found.
......@@ -145,10 +150,14 @@ keys(Cache) ->
%% @doc checks if a key is in the cache (without updating the recent-ness or
%% deleting it for being stale), if not, adds the value. Returns whether found and whether an eviction
%% occurred.
-spec contains_or_add(cache(), term(), term()) ->
-spec contains_or_add(cache(), term(), term() | fun()) ->
{Exists::boolean(), Evict::boolean()}.
contains_or_add(Cache, Key, Value) ->
call(Cache, {contains_or_add, Key, Value}).
Reply = call(Cache, {contains_or_add, Key, Value}),
case Reply of
{error, Error} -> erlang:error(Error);
_ -> Reply
end.
%% @doc remove a key from the cache
-spec remove(cache(), Key::term()) -> ok.
......@@ -220,36 +229,16 @@ init([Opts]) ->
%% @private
handle_call({add, Key, Value}, From, Cache) ->
#cache{items=Items,
evict_list=Evicted} = Cache,
case maps:find(Key, Items) of
error ->
%% add new item
Items2 = maps:put(Key, Value, Items),
Evicted2 = push_front(Evicted, Key),
Cache1 = Cache#cache{items=Items2, evict_list=Evicted2},
%% check if the size is not exceeded
case has_exceeded(Cache1) of
true ->
gen_server:reply(From, true),
{noreply, remove_oldest1(Cache1)};
false ->
{reply, false, Cache1}
end;
{ok, _OldValue} ->
gen_server:reply(From, false),
%% add new value
Items2 = maps:put(Key, Value, Items),
%% move old entry to front
Evicted2 = move_front(Evicted, Key),
{noreply, Cache#cache{items=Items2, evict_list=Evicted2}}
case value(Value) of
{ok, Value2} ->
do_add(Key, Value2, From, Cache);
Error ->
{reply, Error, Cache}
end;
handle_call({get, Key, Default}, _From, Cache) ->
case maps:find(Key, Cache#cache.items) of
{ok, Value} ->
{ok, Value} ->
Evicted2 = move_front(Cache#cache.evict_list, Key),
{reply, Value, Cache#cache{evict_list=Evicted2}};
error ->
......@@ -263,22 +252,11 @@ handle_call({contains, Key}, _From, Cache) ->
{reply, maps:is_key(Key, Cache#cache.items), Cache};
handle_call({contains_or_add, Key, Value}, From, Cache) ->
case maps:is_key(Key, Cache#cache.items) of
false ->
#cache{items=Items,
evict_list=Evicted} = Cache,
Items2 = maps:put(Key, Value, Items),
Evicted2 = push_front(Evicted, Key),
Cache1 = Cache#cache{items=Items2, evict_list=Evicted2},
case has_exceeded(Cache1) of
true ->
gen_server:reply(From, {false, true}),
{noreply, remove_oldest1(Cache1)};
false ->
{reply, {false, false}, Cache1}
end;
true ->
{reply, {true, false}, Cache}
case value(Value) of
{ok, Value2} ->
do_contains_or_add(Key, Value2, From, Cache);
Error ->
{reply, Error, Cache}
end;
handle_call(keys, _From, Cache) ->
......@@ -362,6 +340,70 @@ terminate(_Reason, _Cache) ->
ok.
do_add(Key, Value, From, Cache) ->
#cache{items=Items,
evict_list=Evicted} = Cache,
case maps:find(Key, Items) of
error ->
%% add new item
Items2 = maps:put(Key, Value, Items),
Evicted2 = push_front(Evicted, Key),
Cache1 = Cache#cache{items=Items2, evict_list=Evicted2},
%% check if the size is not exceeded
case has_exceeded(Cache1) of
true ->
gen_server:reply(From, true),
{noreply, remove_oldest1(Cache1)};
false ->
{reply, false, Cache1}
end;
{ok, _OldValue} ->
gen_server:reply(From, false),
%% add new value
Items2 = maps:put(Key, Value, Items),
%% move old entry to front
Evicted2 = move_front(Evicted, Key),
{noreply, Cache#cache{items=Items2, evict_list=Evicted2}}
end.
do_contains_or_add(Key, Value, From, Cache) ->
case maps:is_key(Key, Cache#cache.items) of
false ->
#cache{items=Items,
evict_list=Evicted} = Cache,
Items2 = maps:put(Key, Value, Items),
Evicted2 = push_front(Evicted, Key),
Cache1 = Cache#cache{items=Items2, evict_list=Evicted2},
case has_exceeded(Cache1) of
true ->
gen_server:reply(From, {false, true}),
{noreply, remove_oldest1(Cache1)};
false ->
{reply, {false, false}, Cache1}
end;
true ->
{reply, {true, false}, Cache}
end.
value({M, F, A}) ->
case catch apply(M, F, A) of
{'EXIT', Error} -> {error, Error};
Val -> {ok, Val}
end;
value({F, A}) ->
case catch apply(F, A) of
{'EXIT', Error} -> {error, Error};
Val -> {ok, Val}
end;
value(Fun) when is_function(Fun) ->
case catch Fun() of
{'EXIT', Error} -> {error, Error};
Val -> {ok, Val}
end;
value(Val) ->
{ok, Val}.
call(Cache, Msg) ->
gen_server:call(Cache, Msg).
......@@ -380,11 +422,11 @@ has_exceeded(Cache) ->
end.
push_front(List, Key) ->
[Key | List].
[Key | List].
move_front(List, Key) ->
[Key | lists:delete(Key, List)].
[Key | lists:delete(Key, List)].
remove_until_max(Cache) ->
Cache2 = remove_oldest1(Cache),
......@@ -499,6 +541,13 @@ lru_add_test() ->
lru:stop(Cache),
ok.
lru_add_fun_test() ->
{ok, Cache} = lru:start_link([{max_objs, 1}]),
lru:add(Cache, 1, fun() -> 1 end),
?assert(lru:get(Cache, 1) =:= 1),
lru:stop(Cache),
ok.
lru_contains_test() ->
{ok, Cache} = lru:start_link([{max_objs, 2}]),
lru:add(Cache, 1, 1),
......@@ -520,6 +569,17 @@ lru_contains_or_add_test() ->
lru:stop(Cache),
ok.
lru_contains_or_add_with_fun_test() ->
{ok, Cache} = lru:start_link([{max_objs, 2}]),
lru:add(Cache, 1, fun() -> 1 end),
lru:add(Cache, 2, fun() -> 2 end),
?assertEqual(lru:contains_or_add(Cache, 1, 1), {true, false}),
lru:add(Cache, 3, fun() -> 3 end),
?assertEqual(lru:contains_or_add(Cache, 1, 1), {false, true}),
?assert(lru:contains(Cache, 1)),
lru:stop(Cache),
ok.
lru_peek_test() ->
{ok, Cache} = lru:start_link([{max_objs, 2}]),
lru:add(Cache, 1, 1),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment