Current section
Files
Jump to
Current section
Files
lib/statix/conn_tracker.ex
defmodule Statix.ConnTracker do
@moduledoc false
use GenServer
alias Statix.Conn
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
table =
:ets.new(:statix_conn_tracker, [:set, :protected, :named_table, read_concurrency: true])
{:ok, table}
end
@doc """
Stores a list of connections for the given key, replacing any existing connections.
Closes old connections before storing new ones to prevent resource leaks.
Typically called when establishing a UDS connection pool with pool_size > 1.
"""
@spec set(key :: term(), connections :: [Conn.t()]) :: :ok
def set(key, connections) do
GenServer.call(__MODULE__, {:set, key, connections})
end
@doc """
Retrieves a random connection for the given key.
Returns `{:ok, conn}` with a randomly selected connection from the pool,
or `{:error, :not_found}` if no connections exist for the key.
"""
@spec get(key :: term()) :: {:ok, Conn.t()} | {:error, :not_found}
def get(key) do
case :ets.lookup(:statix_conn_tracker, key) do
[{^key, connections}] ->
random_connection = Enum.random(connections)
{:ok, random_connection}
[] ->
{:error, :not_found}
end
end
@impl true
def handle_call({:set, key, connections}, _from, state) do
# Close old connections before replacing them
case :ets.lookup(state, key) do
[{^key, old_connections}] ->
close_connections(old_connections)
[] ->
:ok
end
:ets.insert(state, {key, connections})
{:reply, :ok, state}
end
@impl true
def terminate(_reason, table) do
# Close all sockets before table is destroyed
:ets.foldl(
fn {_path, connections}, acc ->
close_connections(connections)
acc
end,
nil,
table
)
:ok
end
defp close_connections(connections) do
Enum.each(connections, fn conn ->
if conn.transport == :uds and is_reference(conn.sock) do
:socket.close(conn.sock)
end
end)
end
end