Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions lib/statix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,17 @@ defmodule Statix do
def open(%__MODULE__{conn: %{transport: :uds, sock: {:socket_path, path}} = conn, pool: pool}) do
# UDS sockets are socket references (not ports), so they cannot be registered as process names.
# Instead, store them in ConnTracker's ETS table.
Statix.ConnTracker.ensure_started()

connections =
Enum.map(pool, fn _name ->
Conn.open(conn)
end)

Statix.ConnTracker.set(path, connections)
Statix.ConnTracker.set(path, connections,
conn_template: conn,
pool_size: length(pool)
)
end

def open(%__MODULE__{conn: conn, pool: pool}) do
Expand All @@ -425,7 +430,12 @@ defmodule Statix do

case Statix.ConnTracker.get(path) do
{:ok, conn} ->
Conn.transmit(conn, type, key, to_string(value), options)
case Conn.transmit(conn, type, key, to_string(value), options) do
:ok -> :ok
{:error, _reason} = error ->
Statix.ConnTracker.report_send_error(path)
error
end

{:error, :not_found} ->
{:error, :socket_not_found}
Expand Down
28 changes: 24 additions & 4 deletions lib/statix/application.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
defmodule Statix.Application do
@moduledoc false

use Application
@doc """
Starts the Statix supervision tree lazily on first UDS connection.
No-op if already started. Safe to call concurrently.
"""
def ensure_started do
if GenServer.whereis(Statix.ConnTracker) do
:ok
else
do_start()
end
end

def start(_type, _args) do
defp do_start do
children = [
Statix.ConnTracker
{DynamicSupervisor, strategy: :one_for_one, name: Statix.DynamicSupervisor}
]

Supervisor.start_link(children, strategy: :one_for_one, name: Statix.Supervisor)
case Supervisor.start_link(children, strategy: :one_for_one, name: Statix.Supervisor) do
{:ok, _pid} -> start_conn_tracker()
{:error, {:already_started, _pid}} -> start_conn_tracker()
end
end

defp start_conn_tracker do
case DynamicSupervisor.start_child(Statix.DynamicSupervisor, Statix.ConnTracker) do
{:ok, _pid} -> :ok
{:error, {:already_started, _pid}} -> :ok
end
end
end
38 changes: 32 additions & 6 deletions lib/statix/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,45 @@ defmodule Statix.Conn do
end
end

def transmit(%__MODULE__{sock: sock, prefix: prefix} = conn, type, key, val, options)
def safe_open(%__MODULE__{transport: :uds, sock: {:socket_path, path}} = conn) do
with {:ok, sock} <- :socket.open(:local, :dgram, :default) do
path_addr = %{family: :local, path: String.to_charlist(path)}

case :socket.connect(sock, path_addr) do
:ok ->
{:ok, %__MODULE__{conn | sock: sock}}

{:error, reason} ->
:socket.close(sock)
{:error, reason}
end
end
end

def transmit(
%__MODULE__{sock: sock, prefix: prefix} = conn,
type,
key,
val,
options,
opts \\ []
)
when is_binary(val) and is_list(options) do
result =
prefix
|> Packet.build(type, key, val, options)
|> transmit(conn)

should_log = Keyword.get(opts, :log, true)

with {:error, error} <- result do
Logger.error(fn ->
if(is_atom(sock), do: "", else: "Statix ") <>
"#{inspect(sock)} #{type} metric \"#{key}\" lost value #{val}" <>
" error=#{inspect(error)}"
end)
if should_log do
Logger.error(fn ->
if(is_atom(sock), do: "", else: "Statix ") <>
"#{inspect(sock)} #{type} metric \"#{key}\" lost value #{val}" <>
" error=#{inspect(error)}"
end)
end
end

result
Expand Down
Loading