Packages
macula
0.4.2
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_pubsub_cache.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% LRU cache for remote subscriber lists.
%%% Caches DHT query results to avoid repeated lookups.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_pubsub_cache).
%% API
-export([
new/1,
put/3,
put_with_timestamp/4,
get/2,
invalidate/2,
clear/1,
is_expired/3,
size/1,
max_size/1
]).
%% Types
-type cache_entry() :: #{
pattern := binary(),
subscribers := [map()],
timestamp := integer()
}.
-type cache() :: #{
entries := [cache_entry()], % Ordered: head = most recent, tail = oldest
max_size := pos_integer()
}.
-export_type([cache/0, cache_entry/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Create new cache with max size.
-spec new(pos_integer()) -> cache().
new(MaxSize) ->
#{
entries => [],
max_size => MaxSize
}.
%% @doc Put entry in cache.
-spec put(cache(), binary(), [map()]) -> cache().
put(Cache, Pattern, Subscribers) ->
Timestamp = erlang:system_time(millisecond),
put_with_timestamp(Cache, Pattern, Subscribers, Timestamp).
%% @doc Put entry with custom timestamp (for testing).
-spec put_with_timestamp(cache(), binary(), [map()], integer()) -> cache().
put_with_timestamp(#{entries := Entries, max_size := MaxSize} = Cache, Pattern, Subscribers, Timestamp) ->
Entry = #{
pattern => Pattern,
subscribers => Subscribers,
timestamp => Timestamp
},
%% Remove existing entry for this pattern (if any)
EntriesWithoutPattern = lists:filter(
fun(E) -> maps:get(pattern, E) =/= Pattern end,
Entries
),
%% Add new entry at head (most recent)
NewEntries = [Entry | EntriesWithoutPattern],
%% Enforce max size (evict oldest)
FinalEntries = case length(NewEntries) > MaxSize of
true -> lists:sublist(NewEntries, MaxSize);
false -> NewEntries
end,
Cache#{entries => FinalEntries}.
%% @doc Get entry from cache.
%% Returns {ok, Subscribers, UpdatedCache} or not_found.
%% The updated cache has the entry moved to front (LRU).
-spec get(cache(), binary()) -> {ok, [map()], cache()} | not_found.
get(#{entries := Entries} = Cache, Pattern) ->
case find_entry(Entries, Pattern) of
{found, Entry} ->
%% Move to front (most recently used)
Subscribers = maps:get(subscribers, Entry),
Timestamp = erlang:system_time(millisecond),
UpdatedCache = put_with_timestamp(Cache, Pattern, Subscribers, Timestamp),
{ok, Subscribers, UpdatedCache};
not_found ->
not_found
end.
%% @doc Invalidate (remove) entry.
-spec invalidate(cache(), binary()) -> cache().
invalidate(#{entries := Entries} = Cache, Pattern) ->
NewEntries = lists:filter(
fun(E) -> maps:get(pattern, E) =/= Pattern end,
Entries
),
Cache#{entries => NewEntries}.
%% @doc Clear all entries.
-spec clear(cache()) -> cache().
clear(Cache) ->
Cache#{entries => []}.
%% @doc Check if entry is expired based on TTL.
-spec is_expired(cache(), binary(), pos_integer()) -> boolean().
is_expired(#{entries := Entries}, Pattern, TTL) ->
case find_entry(Entries, Pattern) of
{found, Entry} ->
Timestamp = maps:get(timestamp, Entry),
Now = erlang:system_time(millisecond),
Age = Now - Timestamp,
Age > (TTL * 1000); % TTL in seconds, Age in milliseconds
not_found ->
true % Missing entry is considered expired
end.
%% @doc Get number of entries.
-spec size(cache()) -> non_neg_integer().
size(#{entries := Entries}) ->
length(Entries).
%% @doc Get max size.
-spec max_size(cache()) -> pos_integer().
max_size(#{max_size := MaxSize}) ->
MaxSize.
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Find entry by pattern.
-spec find_entry([cache_entry()], binary()) -> {found, cache_entry()} | not_found.
find_entry([], _Pattern) ->
not_found;
find_entry([Entry | Rest], Pattern) ->
case maps:get(pattern, Entry) of
Pattern -> {found, Entry};
_ -> find_entry(Rest, Pattern)
end.