defmodule Pi.Plugin.Waiters do
@moduledoc "ETS-backed session waiter registry for interactive plugins."
use GenServer
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def install do
case GenServer.whereis(__MODULE__) do
nil -> GenServer.start_link(__MODULE__, [], name: __MODULE__)
_pid -> :ok
end
end
@impl true
def init(_opts), do: {:ok, %{tables: MapSet.new()}}
@impl true
def handle_call({:ensure_table, table}, _from, state) do
create_table(table)
{:reply, :ok, %{state | tables: MapSet.put(state.tables, table)}}
end
def ensure_table!(table) when is_atom(table) do
if table?(table) do
:ok
else
ensure_owned_table!(table)
end
end
def register(table, session_id, pid)
when is_atom(table) and is_binary(session_id) and is_pid(pid) do
ensure_table!(table)
:ets.insert(table, {session_id, pid})
:ok
end
def unregister(table, session_id) when is_atom(table) and is_binary(session_id) do
if table?(table), do: :ets.delete(table, session_id)
:ok
end
def pop(table, session_id) when is_atom(table) and is_binary(session_id) do
if table?(table) do
case :ets.lookup(table, session_id) do
[{^session_id, pid}] ->
:ets.delete(table, session_id)
{:ok, pid}
[] ->
:error
end
else
:error
end
end
def table?(table) when is_atom(table), do: :ets.info(table) != :undefined
defp ensure_owned_table!(table) do
if Process.whereis(__MODULE__) do
GenServer.call(__MODULE__, {:ensure_table, table})
else
raise "#{inspect(__MODULE__)} is not running"
end
end
defp create_table(table) do
case :ets.info(table) do
:undefined -> :ets.new(table, [:named_table, :public, :set])
_info -> table
end
end
end