feat: add Excache.Clients and start_link
This commit is contained in:
1 parent
2775b58d35
commit
8f1cb45a0b
1 file changed
+164
@@ -0,0 +1,164 @@
|
|||||||
|
defmodule Excache.Clients do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
@name __MODULE__
|
||||||
|
@supervisor Excache.Server.ConnectionSupervisor
|
||||||
|
@pubsubRegistry Excache.PubSub.Registry
|
||||||
|
@defaultPoll 500
|
||||||
|
@selfKillDelay 50
|
||||||
|
|
||||||
|
def start_link(_arg), do: GenServer.start_link(__MODULE__, nil, name: @name)
|
||||||
|
|
||||||
|
def snapshot, do: GenServer.call(@name, :snapshot)
|
||||||
|
|
||||||
|
def find(pid), do: GenServer.call(@name, {:find, pid})
|
||||||
|
|
||||||
|
def setName(pid, name), do: GenServer.call(@name, {:setName, pid, name})
|
||||||
|
|
||||||
|
def kill(matcher, caller), do: GenServer.call(@name, {:kill, matcher, caller})
|
||||||
|
|
||||||
|
def subscriptionCount(pid), do: length(Registry.keys(@pubsubRegistry, pid))
|
||||||
|
|
||||||
|
def subscribedChannels do
|
||||||
|
@pubsubRegistry
|
||||||
|
|> Registry.select([{{:"$1", :_, :_}, [], [:"$1"]}])
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> length()
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocked?(pid) do
|
||||||
|
case Process.info(pid, :monitored_by) do
|
||||||
|
{:monitored_by, watchers} -> Process.whereis(Excache.Blocking) in watchers
|
||||||
|
nil -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_arg) do
|
||||||
|
schedule()
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
nextId: 1,
|
||||||
|
clients: %{},
|
||||||
|
monitors: %{},
|
||||||
|
connectionsReceived: 0,
|
||||||
|
closedNetIn: 0,
|
||||||
|
closedNetOut: 0,
|
||||||
|
runId: newRunId(),
|
||||||
|
startedAt: System.system_time(:millisecond)
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call(:snapshot, _from, state) do
|
||||||
|
state = sync(state)
|
||||||
|
{:reply, buildSnapshot(state), state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call({:find, pid}, _from, state) do
|
||||||
|
state = sync(state)
|
||||||
|
{:reply, Map.get(state.clients, pid), state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call({:setName, pid, name}, _from, state) do
|
||||||
|
state = sync(state)
|
||||||
|
|
||||||
|
case Map.fetch(state.clients, pid) do
|
||||||
|
{:ok, client} ->
|
||||||
|
renamed = Map.put(state.clients, pid, %{client | name: name})
|
||||||
|
{:reply, :ok, %{state | clients: renamed}}
|
||||||
|
|
||||||
|
:error ->
|
||||||
|
{:reply, :error, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call({:kill, matcher, caller}, _from, state) do
|
||||||
|
state = sync(state)
|
||||||
|
victims = state.clients |> Map.values() |> Enum.filter(matcher)
|
||||||
|
{itself, others} = Enum.split_with(victims, &(&1.pid == caller))
|
||||||
|
|
||||||
|
Enum.each(others, fn client -> terminate(client.pid) end)
|
||||||
|
|
||||||
|
Enum.each(itself, fn client ->
|
||||||
|
Process.send_after(self(), {:terminate, client.pid}, @selfKillDelay)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:reply, length(victims), state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(:poll, state) do
|
||||||
|
schedule()
|
||||||
|
{:noreply, sync(state)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:terminate, pid}, state) do
|
||||||
|
terminate(pid)
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:DOWN, monitor, :process, pid, _reason}, state) do
|
||||||
|
{_pid, monitors} = Map.pop(state.monitors, monitor)
|
||||||
|
{client, clients} = Map.pop(state.clients, pid)
|
||||||
|
{:noreply, accountClosed(%{state | monitors: monitors, clients: clients}, client)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp schedule do
|
||||||
|
interval = Application.get_env(:excache, :clientsPollInterval, @defaultPoll)
|
||||||
|
Process.send_after(self(), :poll, interval)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sync(state) do
|
||||||
|
Enum.reduce(livePids(), state, fn pid, acc ->
|
||||||
|
case Map.fetch(acc.clients, pid) do
|
||||||
|
{:ok, client} -> %{acc | clients: Map.put(acc.clients, pid, refresh(client))}
|
||||||
|
:error -> register(acc, pid)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp livePids do
|
||||||
|
for {_id, pid, _type, _modules} <- DynamicSupervisor.which_children(@supervisor),
|
||||||
|
is_pid(pid),
|
||||||
|
do: pid
|
||||||
|
end
|
||||||
|
|
||||||
|
defp register(state, pid) do
|
||||||
|
with socket when socket != nil <- socketOf(pid),
|
||||||
|
{netIn, netOut} <- netStats(socket) do
|
||||||
|
now = System.system_time(:millisecond)
|
||||||
|
|
||||||
|
client = %{
|
||||||
|
id: state.nextId,
|
||||||
|
pid: pid,
|
||||||
|
socket: socket,
|
||||||
|
addr: addressOf(socket, &:inet.peername/1),
|
||||||
|
laddr: addressOf(socket, &:inet.sockname/1),
|
||||||
|
name: "",
|
||||||
|
connectedAt: now,
|
||||||
|
lastActiveAt: now,
|
||||||
|
netIn: netIn,
|
||||||
|
netOut: netOut
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
state
|
||||||
|
| nextId: state.nextId + 1,
|
||||||
|
clients: Map.put(state.clients, pid, client),
|
||||||
|
monitors: Map.put(state.monitors, Process.monitor(pid), pid),
|
||||||
|
connectionsReceived: state.connectionsReceived + 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_unavailable -> state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Reference in new issue
Block a user