Current section

Files

Jump to
glimit src glimit@ets_store.erl
Raw

src/glimit@ets_store.erl

-module(glimit@ets_store).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glimit/ets_store.gleam").
-export([new/0, sweep/3, new_with_sweep/2, make_store/1, get_count/1, remove/2]).
-export_type([ets_store/0, ets_table/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" ETS-backed storage backend for rate limiting.\n"
"\n"
" Uses a public ETS table for low-latency, concurrent access without\n"
" the overhead of OTP actor messages.\n"
"\n"
" Note: individual ETS operations are atomic, but the get-then-set\n"
" sequence is not — under very high concurrency a small number of\n"
" extra requests may slip through. This is an acceptable trade-off\n"
" for simplicity and performance in typical workloads.\n"
"\n"
).
-opaque ets_store() :: {ets_store, ets_table()}.
-type ets_table() :: any().
-file("src/glimit/ets_store.gleam", 26).
?DOC(" Create a new ETS-backed store.\n").
-spec new() -> ets_store().
new() ->
{ets_store, glimit_ets_store_ffi:new()}.
-file("src/glimit/ets_store.gleam", 85).
-spec is_idle(
glimit@bucket:bucket_state(),
integer(),
gleam@option:option(integer())
) -> boolean().
is_idle(State, Now, Max_idle_ms) ->
case Max_idle_ms of
none ->
false;
{some, Threshold} ->
case erlang:element(5, State) of
none ->
true;
{some, Last_update} ->
(Now - Last_update) > Threshold
end
end.
-file("src/glimit/ets_store.gleam", 63).
?DOC(" Sweep full and idle buckets from the store.\n").
-spec sweep(ets_store(), integer(), gleam@option:option(integer())) -> integer().
sweep(Store, Now, Max_idle_ms) ->
glimit_ets_store_ffi:sweep(
erlang:element(2, Store),
fun(_, State) ->
glimit@bucket:is_full(State, Now) orelse is_idle(
State,
Now,
Max_idle_ms
)
end
).
-file("src/glimit/ets_store.gleam", 96).
-spec start_sweep_timer(ets_store(), gleam@option:option(integer()), integer()) -> nil.
start_sweep_timer(Store, Max_idle_ms, Sweep_interval_ms) ->
Store_ref = Store,
glimit_ets_store_ffi:set_interval(
Sweep_interval_ms,
fun() ->
Now = glimit@utils:now(),
sweep(Store_ref, Now, Max_idle_ms),
nil
end
).
-file("src/glimit/ets_store.gleam", 35).
?DOC(
" Create a new ETS-backed store with automatic periodic sweeping.\n"
"\n"
" Full and idle buckets are removed every `sweep_interval_ms` milliseconds.\n"
" Set `max_idle_ms` to `None` to disable idle eviction.\n"
).
-spec new_with_sweep(gleam@option:option(integer()), integer()) -> ets_store().
new_with_sweep(Max_idle_ms, Sweep_interval_ms) ->
Store = new(),
start_sweep_timer(Store, Max_idle_ms, Sweep_interval_ms),
Store.
-file("src/glimit/ets_store.gleam", 48).
?DOC(
" Create a `bucket.Store` backed by this ETS table.\n"
"\n"
" Lock and unlock are no-ops since ETS provides per-key atomicity.\n"
).
-spec make_store(ets_store()) -> glimit@bucket:store().
make_store(Store) ->
{store,
fun(Key) ->
case glimit_ets_store_ffi:get(erlang:element(2, Store), Key) of
{ok, State} ->
{ok, {some, State}};
{error, _} ->
{ok, none}
end
end,
fun(Key@1, State@1, _) ->
glimit_ets_store_ffi:set(erlang:element(2, Store), Key@1, State@1)
end,
fun(_) -> {ok, nil} end}.
-file("src/glimit/ets_store.gleam", 75).
?DOC(" Return the number of tracked identifiers.\n").
-spec get_count(ets_store()) -> integer().
get_count(Store) ->
glimit_ets_store_ffi:size(erlang:element(2, Store)).
-file("src/glimit/ets_store.gleam", 81).
?DOC(" Remove an identifier from the store.\n").
-spec remove(ets_store(), binary()) -> {ok, nil} | {error, nil}.
remove(Store, Key) ->
glimit_ets_store_ffi:delete(erlang:element(2, Store), Key).