Packages
macula
0.22.8
7.1.0
7.0.0
6.0.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.8.0
4.7.1
4.7.0
4.6.0
4.5.0
4.4.10
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.1
4.3.0
4.2.9
4.2.8
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.16.0
3.15.3
3.15.2
3.15.1
3.14.0
3.13.0
3.12.1
3.12.0
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.9.0
3.8.0
3.7.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
2.0.0
1.5.2
1.5.1
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.48.6
0.48.5
0.48.4
0.48.3
0.48.2
0.48.1
0.48.0
0.47.1
0.47.0
0.46.3
0.46.1
0.46.0
0.45.3
0.45.2
0.45.1
0.45.0
0.44.2
0.44.1
0.44.0
0.43.3
0.43.2
0.43.1
0.43.0
0.42.9
0.42.8
0.42.7
0.42.6
0.42.5
0.42.4
0.42.3
0.42.2
0.42.1
0.42.0
0.41.1
0.41.0
0.40.1
0.40.0
0.39.9
0.39.8
0.39.7
0.39.6
0.39.5
0.39.4
0.39.3
0.39.2
0.39.1
0.39.0
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.6
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.1
0.34.0
0.33.1
0.33.0
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.9
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.0
0.28.3
0.28.2
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.26.0
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.6
0.24.5
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.7
0.21.6
0.21.5
0.21.4
0.21.2
0.21.1
0.21.0
0.20.25
0.20.24
0.20.23
0.20.22
0.20.21
0.20.20
0.20.19
0.20.18
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.3
0.20.2
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.12.6
0.12.5
0.12.3
0.11.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise
Current section
Files
Jump to
Current section
Files
src/macula_rpc_system/macula_rpc_cache.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% LRU cache for RPC procedure results.
%%% Caches results of idempotent procedures to avoid repeated execution.
%%% Wraps macula_cache with RPC-specific logic and TTL handling.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_rpc_cache).
%% API
-export([
new/1,
put/5,
put_with_timestamp/6,
get/3,
invalidate/3,
clear/1,
is_expired/3,
size/1,
max_size/1,
make_key/2
]).
%% Types
-type cache() :: macula_cache:cache().
-export_type([cache/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Create new cache with max size.
-spec new(pos_integer()) -> cache().
new(MaxSize) ->
macula_cache:new(MaxSize).
%% @doc Make cache key from URI and args.
%% Uses hash of URI and args for consistent key generation.
-spec make_key(binary(), map()) -> binary().
make_key(Uri, Args) ->
%% Sort args by key for consistent hashing
SortedArgs = lists:sort(maps:to_list(Args)),
Data = term_to_binary({Uri, SortedArgs}),
crypto:hash(sha256, Data).
%% @doc Put entry in cache.
-spec put(cache(), binary(), map(), term(), pos_integer()) -> cache().
put(Cache, Uri, Args, Result, TTL) ->
Timestamp = erlang:system_time(millisecond),
put_with_timestamp(Cache, Uri, Args, Result, TTL, Timestamp).
%% @doc Put entry with custom timestamp (for testing).
-spec put_with_timestamp(cache(), binary(), map(), term(), pos_integer(), integer()) -> cache().
put_with_timestamp(Cache, Uri, Args, Result, TTL, Timestamp) ->
Key = make_key(Uri, Args),
Value = #{result => Result, ttl => TTL},
macula_cache:put(Cache, Key, Value, Timestamp).
%% @doc Get entry from cache.
%% Returns {ok, Result, UpdatedCache} or not_found.
%% The updated cache has the entry moved to front (LRU).
%% Automatically removes expired entries.
-spec get(cache(), binary(), map()) -> {ok, term(), cache()} | not_found.
get(Cache, Uri, Args) ->
Key = make_key(Uri, Args),
CacheResult = macula_cache:get(Cache, Key),
do_cache_get(CacheResult, Cache, Key).
%% @private Cache miss
do_cache_get(not_found, _Cache, _Key) ->
not_found;
%% @private Cache hit - check expiration
do_cache_get({ok, Value, TempCache}, Cache, Key) ->
Result = maps:get(result, Value),
Expired = is_value_expired(Value, TempCache, Key),
return_if_valid(Expired, Result, TempCache, Cache, Key).
%% @private Entry expired - remove and return not_found
return_if_valid(true, _Result, _TempCache, Cache, Key) ->
_UpdatedCache = macula_cache:remove(Cache, Key),
not_found;
%% @private Valid entry - return it
return_if_valid(false, Result, TempCache, _Cache, _Key) ->
{ok, Result, TempCache}.
%% @doc Invalidate (remove) entry.
-spec invalidate(cache(), binary(), map()) -> cache().
invalidate(Cache, Uri, Args) ->
Key = make_key(Uri, Args),
macula_cache:remove(Cache, Key).
%% @doc Clear all entries.
-spec clear(cache()) -> cache().
clear(Cache) ->
macula_cache:clear(Cache).
%% @doc Check if entry is expired.
-spec is_expired(cache(), binary(), map()) -> boolean().
is_expired(Cache, Uri, Args) ->
Key = make_key(Uri, Args),
case macula_cache:get(Cache, Key) of
{ok, Value, UpdatedCache} ->
is_value_expired(Value, UpdatedCache, Key);
not_found ->
true % Missing entry is considered expired
end.
%% @doc Get number of entries.
-spec size(cache()) -> non_neg_integer().
size(Cache) ->
macula_cache:size(Cache).
%% @doc Get max size.
-spec max_size(cache()) -> pos_integer().
max_size(Cache) ->
macula_cache:max_size(Cache).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Check if value is expired based on its TTL and timestamp.
-spec is_value_expired(map(), cache(), term()) -> boolean().
is_value_expired(Value, Cache, Key) ->
TTL = maps:get(ttl, Value),
%% Get timestamp from cache entry
case get_entry_timestamp(Cache, Key) of
{ok, Timestamp} ->
Now = erlang:system_time(millisecond),
Age = Now - Timestamp,
Age > (TTL * 1000); % TTL in seconds, Age in milliseconds
not_found ->
true % No timestamp means expired
end.
%% @doc Get timestamp of entry (internal helper).
-spec get_entry_timestamp(cache(), term()) -> {ok, integer()} | not_found.
get_entry_timestamp(#{entries := Entries}, Key) ->
case lists:search(fun(E) -> maps:get(key, E) =:= Key end, Entries) of
{value, Entry} ->
{ok, maps:get(timestamp, Entry)};
false ->
not_found
end.