Reduce cyclomatic complexity in clustering, encoding, and leader_election

Simplify complex functions to improve readability and maintainability:
- clustering.ex: Convert case statement to map lookup for cluster radii
- encoding.ex: Extract codepoint validation into separate function clauses
- leader_election.ex: Extract PID liveness checking and cleanup logic into helpers

Co-Authored-By: Graham <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2026-02-09 11:57:50 -06:00
parent 48be64583a
commit d48a3a291f
No known key found for this signature in database
3 changed files with 69 additions and 70 deletions

View file

@ -186,53 +186,53 @@ defmodule Aprsme.Cluster.LeaderElection do
defp cleanup_stale_registrations do defp cleanup_stale_registrations do
case :global.whereis_name(@election_key) do case :global.whereis_name(@election_key) do
:undefined -> :undefined ->
# No registration exists
:ok :ok
pid when is_pid(pid) -> pid when is_pid(pid) ->
pid_node = node(pid) check_and_cleanup_registration(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
end end
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 defp get_cluster_wide_status do
all_nodes = [node() | Node.list()] all_nodes = [node() | Node.list()]

View file

@ -155,25 +155,21 @@ defmodule Aprsme.Encoding do
@spec valid_grapheme?(String.grapheme()) :: boolean() @spec valid_grapheme?(String.grapheme()) :: boolean()
defp valid_grapheme?(grapheme) do defp valid_grapheme?(grapheme) do
case String.to_charlist(grapheme) do case String.to_charlist(grapheme) do
[cp] -> [cp] -> valid_codepoint?(cp)
# Allow tab (0x09), newline (0x0A), carriage return (0x0D) _ -> true
# 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
end end
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 @spec find_invalid_byte_position(binary()) :: non_neg_integer() | nil
defp find_invalid_byte_position(input) do defp find_invalid_byte_position(input) do
input input

View file

@ -46,21 +46,24 @@ defmodule Aprsme.Packets.Clustering do
Calculates the clustering radius in degrees based on zoom level. Calculates the clustering radius in degrees based on zoom level.
Lower zoom levels get larger radii for more aggressive clustering. 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() @spec calculate_cluster_radius(integer()) :: float()
def calculate_cluster_radius(zoom) do def calculate_cluster_radius(zoom) do
# More aggressive scaling for better separation at higher zooms Map.get(@cluster_radii, zoom, @default_radius)
# 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
end end
# Filter packets with valid lat/lon coordinates # Filter packets with valid lat/lon coordinates