Fix leader election: prevent self-deregistration on re-election

:global.register_name returns :no even when the name is already
registered to the calling PID. This caused the leader to clear its
own leadership state every 5 seconds after cluster formation triggered
a redundant re-election attempt.

Now checks if we already hold the registration before attempting to
re-register, preserving leadership state across cluster topology changes.
This commit is contained in:
Graham McIntire 2026-02-19 17:21:18 -06:00
parent dda4bbce15
commit 66061a5abe
No known key found for this signature in database

View file

@ -126,19 +126,21 @@ defmodule Aprsme.Cluster.LeaderElection do
# First, try to clean up any stale registrations
cleanup_stale_registrations()
case :global.register_name(@election_key, self(), &resolve_conflict/3) do
:yes ->
Logger.info("Elected as APRS-IS connection leader on node #{node()}")
:persistent_term.put({__MODULE__, :is_leader}, true)
notify_leadership_change(true)
# Check if we already hold the registration — re-registering the same
# name returns :no even for the same PID, which would incorrectly clear
# our leadership state.
case :global.whereis_name(@election_key) do
pid when pid == self() ->
if !state.is_leader do
Logger.info("Re-confirming leadership on node #{node()}")
:persistent_term.put({__MODULE__, :is_leader}, true)
notify_leadership_change(true)
end
{:noreply, %{state | is_leader: true, leader_node: node()}}
:no ->
leader_pid = :global.whereis_name(@election_key)
leader_node = if leader_pid != :undefined and is_pid(leader_pid), do: node(leader_pid)
Logger.info("Not elected as leader. Current leader is on node #{inspect(leader_node)}")
:persistent_term.put({__MODULE__, :is_leader}, false)
{:noreply, %{state | is_leader: false, leader_node: leader_node}}
_ ->
attempt_registration(state)
end
end
@ -183,6 +185,23 @@ defmodule Aprsme.Cluster.LeaderElection do
:ok
end
defp attempt_registration(state) do
case :global.register_name(@election_key, self(), &resolve_conflict/3) do
:yes ->
Logger.info("Elected as APRS-IS connection leader on node #{node()}")
:persistent_term.put({__MODULE__, :is_leader}, true)
notify_leadership_change(true)
{:noreply, %{state | is_leader: true, leader_node: node()}}
:no ->
leader_pid = :global.whereis_name(@election_key)
leader_node = if leader_pid != :undefined and is_pid(leader_pid), do: node(leader_pid)
Logger.info("Not elected as leader. Current leader is on node #{inspect(leader_node)}")
:persistent_term.put({__MODULE__, :is_leader}, false)
{:noreply, %{state | is_leader: false, leader_node: leader_node}}
end
end
# Conflict resolution - prefer the process on the lexicographically lower node
defp resolve_conflict(_name, pid1, pid2) do
node1 = node(pid1)