Current section
Files
Jump to
Current section
Files
src/glimit@rate_limiter.erl
-module(glimit@rate_limiter).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glimit/rate_limiter.gleam").
-export([hit/2, set_now/2, new/3]).
-export_type([hit_error/0, state/1, message/1]).
-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(false).
-type hit_error() :: rate_limited | unavailable | store_lock_failed.
-type state(FWW) :: {state,
fun((FWW) -> integer()),
fun((FWW) -> integer()),
glimit@bucket:store(),
gleam@option:option(integer())}.
-type message(FWX) :: {hit,
FWX,
gleam@erlang@process:subject({ok, nil} | {error, hit_error()})} |
{set_now, integer(), gleam@erlang@process:subject(nil)}.
-file("src/glimit/rate_limiter.gleam", 59).
?DOC(false).
-spec get_now(state(any())) -> integer().
get_now(State) ->
case erlang:element(5, State) of
{some, Now} ->
Now;
none ->
glimit@utils:now()
end.
-file("src/glimit/rate_limiter.gleam", 117).
?DOC(false).
-spec compute_ttl(glimit@bucket:bucket_state()) -> integer().
compute_ttl(B) ->
case erlang:element(3, B) > 0 of
true ->
Refill_seconds = case erlang:element(3, B) of
0 -> 0;
Gleam@denominator -> ((erlang:element(2, B) + erlang:element(
3,
B
))
- 1)
div Gleam@denominator
end,
gleam@int:max(Refill_seconds * 2, 60);
false ->
60
end.
-file("src/glimit/rate_limiter.gleam", 176).
?DOC(false).
-spec hit(gleam@erlang@process:subject(message(FXT)), FXT) -> {ok, nil} |
{error, hit_error()}.
hit(Rate_limiter, Identifier) ->
case glimit@utils:safe_call(
Rate_limiter,
fun(_capture) -> {hit, Identifier, _capture} end,
1000
) of
{ok, {ok, nil}} ->
{ok, nil};
{ok, {error, Err}} ->
{error, Err};
{error, nil} ->
{error, unavailable}
end.
-file("src/glimit/rate_limiter.gleam", 190).
?DOC(false).
-spec set_now(gleam@erlang@process:subject(message(any())), integer()) -> nil.
set_now(Rate_limiter, Now) ->
_ = glimit@utils:safe_call(
Rate_limiter,
fun(_capture) -> {set_now, Now, _capture} end,
1000
),
nil.
-file("src/glimit/rate_limiter.gleam", 113).
?DOC(false).
-spec string_key(any()) -> binary().
string_key(Identifier) ->
<<"glimit:"/utf8, (gleam@string:inspect(Identifier))/binary>>.
-file("src/glimit/rate_limiter.gleam", 66).
?DOC(false).
-spec handle_store_hit(state(FXD), FXD) -> {ok, nil} | {error, hit_error()}.
handle_store_hit(State, Identifier) ->
Key = string_key(Identifier),
case (erlang:element(4, erlang:element(4, State)))(Key) of
{error, _} ->
{error, store_lock_failed};
{ok, _} ->
Bucket_result = case (erlang:element(2, erlang:element(4, State)))(
Key
) of
{ok, {some, B}} ->
{ok, B};
{ok, none} ->
gleam@result:'try'(
glimit_ffi:rescue(
fun() -> (erlang:element(2, State))(Identifier) end
),
fun(Max) ->
gleam@result:'try'(
glimit_ffi:rescue(
fun() ->
(erlang:element(3, State))(Identifier)
end
),
fun(Rate) -> glimit@bucket:new(Max, Rate) end
)
end
);
{error, _} ->
_ = (erlang:element(5, erlang:element(4, State)))(Key),
{error, nil}
end,
case Bucket_result of
{error, _} ->
_ = (erlang:element(5, erlang:element(4, State)))(Key),
{error, unavailable};
{ok, B@1} ->
Now = get_now(State),
{Hit_result, New_b} = glimit@bucket:hit(B@1, Now),
Ttl = compute_ttl(New_b),
_ = (erlang:element(3, erlang:element(4, State)))(
Key,
New_b,
Ttl
),
_ = (erlang:element(5, erlang:element(4, State)))(Key),
case Hit_result of
{ok, nil} ->
{ok, nil};
{error, nil} ->
{error, rate_limited}
end
end
end.
-file("src/glimit/rate_limiter.gleam", 129).
?DOC(false).
-spec handle_message(state(FXI), message(FXI)) -> gleam@otp@actor:next(state(FXI), message(FXI)).
handle_message(State, Message) ->
case Message of
{hit, Identifier, Client} ->
Result = handle_store_hit(State, Identifier),
gleam@otp@actor:send(Client, Result),
gleam@otp@actor:continue(State);
{set_now, Now, Client@1} ->
gleam@otp@actor:send(Client@1, nil),
gleam@otp@actor:continue(
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
{some, Now}}
)
end.
-file("src/glimit/rate_limiter.gleam", 149).
?DOC(false).
-spec new(
fun((FXP) -> integer()),
fun((FXP) -> integer()),
glimit@bucket:store()
) -> {ok, gleam@erlang@process:subject(message(FXP))} | {error, nil}.
new(Per_second, Burst_limit, Store) ->
_pipe@1 = gleam@otp@actor:new_with_initialiser(
1000,
fun(Self_subject) ->
State = {state, Burst_limit, Per_second, Store, none},
{ok,
begin
_pipe = gleam@otp@actor:initialised(State),
gleam@otp@actor:returning(_pipe, Self_subject)
end}
end
),
_pipe@2 = gleam@otp@actor:on_message(_pipe@1, fun handle_message/2),
_pipe@3 = gleam@otp@actor:start(_pipe@2),
_pipe@4 = gleam@result:map(
_pipe@3,
fun(Started) -> erlang:element(3, Started) end
),
gleam@result:map_error(_pipe@4, fun(_) -> nil end).