Current section
Files
Jump to
Current section
Files
lib/global_node_manager.ex
defmodule HareMq.GlobalNodeManager do
require Logger
@doc """
Waits until the `:global` name registry is synchronized across all connected
nodes before returning, with a 30-second timeout.
Two cases:
* **Single-node** (`Node.list()` is empty) ā no remote nodes to synchronize
with, so we return immediately. `:global` registration on a single node is
always instantaneous.
* **Multi-node** ā we call `:global.sync/0` which blocks until the registry
has been reconciled across all connected nodes. This ensures that a
subsequent `GenServer.start_link(..., name: {:global, name})` call will not
race with an identical registration on another node.
Called from `HareMq.Worker.Consumer.start_link/1` before registering the
consumer globally.
"""
def wait_for_all_nodes_ready(name) do
wait_until_ready(name, 30_000)
end
defp wait_until_ready(_name, remaining_time) when remaining_time <= 0 do
Logger.error("[GlobalNodeManager] Timeout waiting for :global sync.")
{:error, :timeout}
end
defp wait_until_ready(name, remaining_time) do
if Node.list() == [] do
# Single-node deployment ā nothing to synchronize.
:ok
else
case :global.sync() do
:ok ->
# After sync, check if another node already registered this name.
# If so, our own GenServer.start_link will return {:already_started, pid}
# which CodeFlow.successful_start/1 converts to :ignore ā this log
# just makes that situation visible before it happens.
case :global.whereis_name(name) do
pid when is_pid(pid) ->
Logger.debug(
"[GlobalNodeManager] #{inspect(name)} already registered globally on node #{inspect(node(pid))}."
)
:undefined ->
Logger.debug("[GlobalNodeManager] :global registry synced for #{inspect(name)}.")
end
:ok
{:error, reason} ->
Logger.warning("[GlobalNodeManager] :global sync failed (#{inspect(reason)}), retrying...")
Process.sleep(1_000)
wait_until_ready(name, remaining_time - 1_000)
end
end
end
end