Hermolaos.Client.RequestTracker (Hermolaos v0.3.0)
View SourceTracks pending JSON-RPC requests and correlates them with responses.
The RequestTracker maintains a mapping of request IDs to caller information, enabling the Connection to route responses back to the correct caller.
Features
- Monotonically increasing integer IDs for efficiency
- ETS-backed storage for O(1) lookups and concurrent access
- Automatic timeout handling with configurable defaults
- Statistics tracking for monitoring
Design Notes
This module uses ETS for storage because:
- Performance: O(1) lookups regardless of pending request count
- Concurrency: ETS tables support concurrent reads without locking
- Isolation: Each tracker has its own table, crashes don't affect others
Example
{:ok, tracker} = Hermolaos.Client.RequestTracker.start_link(timeout: 30_000)
# Track a request
id = Hermolaos.Client.RequestTracker.next_id(tracker)
:ok = Hermolaos.Client.RequestTracker.track(tracker, id, "tools/list", from)
# When response arrives, complete the request
{:ok, from, method} = Hermolaos.Client.RequestTracker.complete(tracker, id)
GenServer.reply(from, result)
Summary
Functions
Cancels a pending request.
Returns a specification to start this module under a supervisor.
Completes a pending request successfully.
Fails a pending request with an error.
Fails all pending requests (e.g., when connection closes).
Gets the next request ID (monotonically increasing integer).
Checks if a request ID is currently pending.
Returns the number of pending requests.
Starts a new request tracker.
Returns tracker statistics.
Tracks a pending request.
Types
@type from() :: GenServer.from()
@type id() :: integer()
@type method() :: String.t()
@type stats() :: %{ requests_tracked: non_neg_integer(), requests_completed: non_neg_integer(), requests_failed: non_neg_integer(), requests_timed_out: non_neg_integer(), requests_cancelled: non_neg_integer() }
@type t() :: GenServer.server()
Functions
Cancels a pending request.
Examples
:ok = Hermolaos.Client.RequestTracker.cancel(tracker, 1)
Returns a specification to start this module under a supervisor.
See Supervisor.
Completes a pending request successfully.
Returns the original caller's from and method so the caller can be notified.
Examples
case Hermolaos.Client.RequestTracker.complete(tracker, 1) do
{:ok, from, "tools/list"} ->
GenServer.reply(from, {:ok, result})
{:error, :not_found} ->
# Request already completed, timed out, or never existed
:ok
end
Fails a pending request with an error.
Examples
case Hermolaos.Client.RequestTracker.fail(tracker, 1, error) do
{:ok, from, method} ->
GenServer.reply(from, {:error, error})
{:error, :not_found} ->
:ok
end
Fails all pending requests (e.g., when connection closes).
Returns the list of failed requests with their callers.
Examples
failed = Hermolaos.Client.RequestTracker.fail_all(tracker, {:error, :connection_closed})
for {from, method} <- failed do
GenServer.reply(from, {:error, :connection_closed})
end
Gets the next request ID (monotonically increasing integer).
Examples
id = Hermolaos.Client.RequestTracker.next_id(tracker)
# => 1
id = Hermolaos.Client.RequestTracker.next_id(tracker)
# => 2
Checks if a request ID is currently pending.
@spec pending_count(t()) :: non_neg_integer()
Returns the number of pending requests.
Examples
count = Hermolaos.Client.RequestTracker.pending_count(tracker)
# => 5
Starts a new request tracker.
Options
:timeout- Default timeout for requests in ms (default: 30000):name- GenServer name (optional)
Examples
{:ok, tracker} = Hermolaos.Client.RequestTracker.start_link()
{:ok, tracker} = Hermolaos.Client.RequestTracker.start_link(timeout: 60_000)
Returns tracker statistics.
Examples
stats = Hermolaos.Client.RequestTracker.stats(tracker)
# => %{requests_tracked: 100, requests_completed: 95, ...}
Tracks a pending request.
Parameters
tracker- The tracker processid- Request ID (fromnext_id/1)method- JSON-RPC method namefrom- GenServer from tuple for replytimeout- Optional timeout override in ms
Examples
:ok = Hermolaos.Client.RequestTracker.track(tracker, 1, "tools/list", from)
:ok = Hermolaos.Client.RequestTracker.track(tracker, 2, "tools/call", from, 60_000)