diff --git a/lib/aprsme/cluster/leader_election.ex b/lib/aprsme/cluster/leader_election.ex index 3b94689..99036f2 100644 --- a/lib/aprsme/cluster/leader_election.ex +++ b/lib/aprsme/cluster/leader_election.ex @@ -186,53 +186,53 @@ defmodule Aprsme.Cluster.LeaderElection do defp cleanup_stale_registrations do case :global.whereis_name(@election_key) do :undefined -> - # No registration exists :ok pid when is_pid(pid) -> - pid_node = node(pid) - connected_nodes = [node() | Node.list()] - - # Check if the PID's node is still connected - if pid_node in connected_nodes do - # Node is connected, try to check if process is alive - try do - if pid_node == node() do - # Local PID - use Process.alive? - if Process.alive?(pid) do - :ok - else - Logger.info("Cleaning up stale leader registration for dead local process #{inspect(pid)}") - :global.unregister_name(@election_key) - end - else - # Remote PID - use RPC to check if alive - case :rpc.call(pid_node, Process, :alive?, [pid]) do - true -> - :ok - - false -> - Logger.info("Cleaning up stale leader registration for dead remote process #{inspect(pid)}") - :global.unregister_name(@election_key) - - {:badrpc, _reason} -> - Logger.info("Cleaning up stale leader registration for unreachable process #{inspect(pid)}") - :global.unregister_name(@election_key) - end - end - rescue - _error -> - Logger.info("Cleaning up stale leader registration for problematic process #{inspect(pid)}") - :global.unregister_name(@election_key) - end - else - # Node is disconnected - clean up the registration - Logger.info("Cleaning up stale leader registration for disconnected node #{pid_node}") - :global.unregister_name(@election_key) - end + check_and_cleanup_registration(pid) end end + defp check_and_cleanup_registration(pid) do + pid_node = node(pid) + connected_nodes = [node() | Node.list()] + + if pid_node in connected_nodes do + check_pid_liveness(pid, pid_node) + else + cleanup_registration("disconnected node #{pid_node}") + end + end + + defp check_pid_liveness(pid, pid_node) do + if is_pid_alive?(pid, pid_node) do + :ok + else + reason = if pid_node == node(), do: "dead local process", else: "dead remote process" + cleanup_registration("#{reason} #{inspect(pid)}") + end + rescue + _error -> + cleanup_registration("problematic process #{inspect(pid)}") + end + + defp is_pid_alive?(pid, pid_node) when pid_node == node() do + Process.alive?(pid) + end + + defp is_pid_alive?(pid, pid_node) do + if :rpc.call(pid_node, Process, :alive?, [pid]) do + true + else + false + end + end + + defp cleanup_registration(reason) do + Logger.info("Cleaning up stale leader registration for #{reason}") + :global.unregister_name(@election_key) + end + defp get_cluster_wide_status do all_nodes = [node() | Node.list()] diff --git a/lib/aprsme/encoding.ex b/lib/aprsme/encoding.ex index 7ede38f..c91412b 100644 --- a/lib/aprsme/encoding.ex +++ b/lib/aprsme/encoding.ex @@ -155,25 +155,21 @@ defmodule Aprsme.Encoding do @spec valid_grapheme?(String.grapheme()) :: boolean() defp valid_grapheme?(grapheme) do case String.to_charlist(grapheme) do - [cp] -> - # Allow tab (0x09), newline (0x0A), carriage return (0x0D) - # Remove other control characters - case cp do - 9 -> true - 10 -> true - 13 -> true - c when c >= 0 and c <= 31 -> false - 127 -> false - c when c >= 128 and c <= 159 -> false - _ -> true - end - - _ -> - # Multi-codepoint grapheme, keep it - true + [cp] -> valid_codepoint?(cp) + _ -> true end end + # Allow tab (0x09), newline (0x0A), carriage return (0x0D) + # Remove other control characters + defp valid_codepoint?(9), do: true + defp valid_codepoint?(10), do: true + defp valid_codepoint?(13), do: true + defp valid_codepoint?(127), do: false + defp valid_codepoint?(c) when c >= 0 and c <= 31, do: false + defp valid_codepoint?(c) when c >= 128 and c <= 159, do: false + defp valid_codepoint?(_), do: true + @spec find_invalid_byte_position(binary()) :: non_neg_integer() | nil defp find_invalid_byte_position(input) do input diff --git a/lib/aprsme/packets/clustering.ex b/lib/aprsme/packets/clustering.ex index f487d97..7f1228e 100644 --- a/lib/aprsme/packets/clustering.ex +++ b/lib/aprsme/packets/clustering.ex @@ -46,21 +46,24 @@ defmodule Aprsme.Packets.Clustering do Calculates the clustering radius in degrees based on zoom level. Lower zoom levels get larger radii for more aggressive clustering. """ + # Clustering radius lookup table by zoom level + # More aggressive scaling for better separation at higher zooms + # Zoom 1: ~5 degrees, Zoom 5: ~0.3 degrees, Zoom 8: ~0.04 degrees + @cluster_radii %{ + 1 => 5.0, + 2 => 2.5, + 3 => 1.25, + 4 => 0.625, + 5 => 0.3125, + 6 => 0.15625, + 7 => 0.078125, + 8 => 0.0390625 + } + @default_radius 0.0390625 + @spec calculate_cluster_radius(integer()) :: float() def calculate_cluster_radius(zoom) do - # More aggressive scaling for better separation at higher zooms - # Zoom 1: ~5 degrees, Zoom 5: ~0.3 degrees, Zoom 8: ~0.04 degrees - case zoom do - 1 -> 5.0 - 2 -> 2.5 - 3 -> 1.25 - 4 -> 0.625 - 5 -> 0.3125 - 6 -> 0.15625 - 7 -> 0.078125 - 8 -> 0.0390625 - _ -> 0.0390625 - end + Map.get(@cluster_radii, zoom, @default_radius) end # Filter packets with valid lat/lon coordinates