Current section
Files
Jump to
Current section
Files
lib/trip_switch.ex
defmodule TripSwitch do
@moduledoc """
Documentation for `TripSwitch`.
"""
use GenServer
alias TripSwitch.Circuit
@event_prefix :trip_switch
@spec broken?(atom()) :: boolean()
def broken?(id), do: Circuit.broken?(get(id))
@spec send(atom(), Circuit.signal()) :: {:ok, term()} | :broken
def send(id, signal) do
:telemetry.span([@event_prefix, :signal], %{id: id}, fn ->
with %Circuit{} = circuit <- get(id),
{result, circuit} <- Circuit.handle(circuit, signal),
:ok <- GenServer.call(via(id), {:save, circuit}) do
{result, %{id: id}}
end
end)
end
@spec get(atom()) :: Circuit.t()
def get(id), do: GenServer.call(via(id), :get)
@spec reset(atom()) :: :ok
def reset(id), do: GenServer.call(via(id), :reset)
@spec child_spec(keyword()) :: Supervisor.child_spec()
def child_spec(opts) do
unless name = opts[:name] do
raise ArgumentError, "expected :name option to be present"
end
Supervisor.child_spec(super(opts), id: {__MODULE__, name})
end
@impl GenServer
def init(opts), do: {:ok, %{circuit: Circuit.new(opts), repair: nil}}
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts) do
name = Keyword.fetch!(opts, :name)
GenServer.start_link(__MODULE__, opts, name: via(name))
end
@impl GenServer
def handle_call(:get, _from, %{circuit: circuit} = state) do
{:reply, circuit, state}
end
def handle_call(:reset, _from, %{circuit: circuit} = state) do
state = cancel_timer(%{state | circuit: Circuit.reset(circuit)})
{:reply, :ok, state}
end
def handle_call({:save, circuit}, _from, state) do
state = schedule_or_cancel_repair(circuit, state)
{:reply, :ok, state}
end
@impl GenServer
def handle_info(:repair, %{circuit: circuit} = state) do
[id] = Registry.keys(TripSwitch.Registry, self())
state =
Circuit.repair(circuit)
|> schedule_or_cancel_repair(state)
:telemetry.execute([@event_prefix, :repair, :done], %{}, %{id: id})
{:noreply, state}
end
defp schedule_or_cancel_repair(%Circuit{fix_after: at} = circuit, state) do
with {:a, true} <- {:a, Circuit.repairable?(circuit)},
{:b, false} <- {:b, is_reference(state.repair)},
timer <- Process.send_after(self(), :repair, at) do
%{state | circuit: circuit, repair: timer}
else
{:a, false} -> cancel_timer(%{state | circuit: circuit})
{:b, true} -> %{state | circuit: circuit}
end
end
defp cancel_timer(state) do
case state.repair do
ref when is_reference(ref) ->
Process.cancel_timer(ref)
%{state | repair: nil}
nil ->
state
end
end
defp via(id), do: {:via, Registry, {TripSwitch.Registry, id}}
end