An in-memory sliding-window rate limiter for connected Erlang nodes.
Build a limit with GlobalRateLimiter.Limit.new/2, then see try_consume/2,
consume/3, and remaining/1 for the public API.
Summary
Functions
Waits up to timeout until a count can be consumed.
Gets the remaining count for the given limit.
Immediately tries to consume a count against the limit.
Types
@type retry_after() :: pos_integer()
@type wait_timeout() :: non_neg_integer() | :infinity
Functions
@spec consume(GlobalRateLimiter.Limit.t(), pos_integer(), wait_timeout()) :: {:ok, non_neg_integer()} | {:error, :timeout | :count_exceeds_limit}
Waits up to timeout until a count can be consumed.
The calling process is monitored while it waits. If it exits, its queue entry is removed without consuming anything.
timeout is measured in milliseconds. Pass :infinity to wait without a
deadline.
Returns {:error, :timeout} when the timeout elapses, or
{:error, :count_exceeds_limit} when count can never fit within the limit.
iex> limit = GlobalRateLimiter.Limit.new({:job, make_ref()}, window: 60_000, limit: 1)
iex> GlobalRateLimiter.consume(limit, 1, :infinity)
{:ok, 0}
@spec remaining(GlobalRateLimiter.Limit.t()) :: non_neg_integer()
Gets the remaining count for the given limit.
iex> limit = GlobalRateLimiter.Limit.new({:ip, "a.b.c.d", make_ref()}, window: 60_000, limit: 1)
iex> GlobalRateLimiter.remaining(limit)
1
iex> GlobalRateLimiter.try_consume(limit, 1)
{:ok, 0}
iex> GlobalRateLimiter.remaining(limit)
0
@spec try_consume(GlobalRateLimiter.Limit.t(), pos_integer()) :: {:ok, non_neg_integer()} | {:error, {:rate_limited, retry_after()} | :count_exceeds_limit}
Immediately tries to consume a count against the limit.
Returns {:ok, remaining_count} on success.
Returns {:error, {:rate_limited, retry_after}} when current consumption
leaves insufficient capacity. retry_after is a millisecond hint based on
the current cluster snapshot.
Returns {:error, :count_exceeds_limit} when count can never fit within
the limit.
iex> limit = GlobalRateLimiter.Limit.new({:ip, "a.b.c.d", make_ref()}, window: 60_000, limit: 1)
iex> GlobalRateLimiter.try_consume(limit, 1)
{:ok, 0}
iex> match?(
...> {:error, {:rate_limited, retry_after}} when is_integer(retry_after),
...> GlobalRateLimiter.try_consume(limit, 1)
...> )
true