Packages

Throttler implementation with multi-rate support.

Current section

Files

Jump to
throttex lib throttex.ex
Raw

lib/throttex.ex

defmodule Throttex do
@moduledoc """
Throttex is a GenServer which provides interface for rate limit control.
Basically it provides two main functions: *request* and *release*.
Performing _request_, it allows or disallows operation accordingly to the *Rate Limit* rules.
When the _request_ has been completed, _release_ is expected to be called to confirm operation.
"""
use GenServer
require Logger
alias Throttex.Types
alias Throttex.Core
defmacro __using__(_opts \\ []) do
quote location: :keep do
defdelegate start(args), to: Throttex
defdelegate start_link(args), to: Throttex
defdelegate child_spec(args), to: Throttex
end
end
def start(args) do
args = validate_args(args)
GenServer.start(__MODULE__, args, name: args[:name])
end
def start_link(args) do
args = validate_args(args)
GenServer.start_link(__MODULE__, args, name: args[:name])
end
@impl true
def init(args) do
{:ok, Core.initial_state(args[:rates])}
end
@spec request(GenServer.server(), rate_id :: term()) ::
{:ok, request_id :: term()}
| {:error, :invalid_rate | :unavailable}
def request(pid \\ __MODULE__, rate_id) do
GenServer.call(pid, {:request, rate_id})
end
@spec release(GenServer.server(), Types.timestamp()) :: :ok
def release(pid \\ __MODULE__, request_id) do
GenServer.cast(pid, {:release, request_id})
end
@impl true
def handle_call({:request, rate_id}, _from, state) do
case Core.request(state, rate_id, timestamp()) do
{:ok, request_id, new_state} ->
{:reply, {:ok, request_id}, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call(message, _from, state) do
Logger.warning("Got unexpected message in handle_call/3: #{inspect(message)}")
{:reply, {:error, :unsupported_method}, state}
end
@impl true
def handle_cast({:release, request_id}, state) do
case Core.release(state, request_id, timestamp()) do
{:ok, new_state} -> {:noreply, new_state}
:error -> {:noreply, state}
end
end
@impl true
def handle_cast(message, state) do
Logger.warning("Got unexpected message in handle_cast/2: #{inspect(message)}")
{:noreply, state}
end
@impl true
def handle_info(message, state) do
Logger.warning("Got unexpected message in handle_info/2: #{inspect(message)}")
{:noreply, state}
end
defp timestamp(), do: System.system_time(:millisecond)
defp validate_args(args) do
[
name: args[:name],
rates: Keyword.fetch!(args, :rates)
]
end
end