GlobalRateLimiter (global_rate_limiter v0.2.0)

Copy Markdown View Source

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

retry_after()

@type retry_after() :: pos_integer()

wait_timeout()

@type wait_timeout() :: non_neg_integer() | :infinity

Functions

consume(limit, count, timeout)

@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}

remaining(limit)

@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

try_consume(limit, count)

@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