defmodule Plausible.Session.Balancer do @moduledoc "Serialize session processing to avoid explicit locks" use GenServer def start_link(id) do GenServer.start_link(__MODULE__, id, name: via(id)) end @impl true def init(id) do {:ok, %{id: id}} end def dispatch(user_id, fun, opts) do timeout = Keyword.fetch!(opts, :timeout) local? = Keyword.get(opts, :local?, false) if local? do fun.() else worker = :erlang.phash2(user_id, Plausible.Session.BalancerSupervisor.size()) + 1 [{pid, _}] = Registry.lookup(Plausible.Session.Balancer.Registry, worker) GenServer.call(pid, {:process, fun}, timeout) end end @impl true def handle_call({:process, fun}, _from, state) do try do response = fun.() {:reply, response, state} rescue e -> {:reply, {:error, e}, state} end end defp via(id), do: {:via, Registry, {Plausible.Session.Balancer.Registry, id}} end