diff --git a/lib/aprsme/device_cache.ex b/lib/aprsme/device_cache.ex index dbc6495..625a63d 100644 --- a/lib/aprsme/device_cache.ex +++ b/lib/aprsme/device_cache.ex @@ -101,28 +101,34 @@ defmodule Aprsme.DeviceCache do defp load_devices_into_cache do require Logger - try do - devices = - try do - Repo.all(Devices) - rescue - error -> - Logger.error("Failed to load devices from database: #{inspect(error)}") - [] - end + # Skip database loading in test environment + if Application.get_env(:aprsme, :env) == :test do + Cache.put(@cache_name, :all_devices, []) + :ok + else + try do + devices = + try do + Repo.all(Devices) + rescue + error -> + Logger.error("Failed to load devices from database: #{inspect(error)}") + [] + end - # Store all devices in cache - case Cache.put(@cache_name, :all_devices, devices) do - {:ok, true} -> :ok - error -> error + # Store all devices in cache + case Cache.put(@cache_name, :all_devices, devices) do + {:ok, true} -> :ok + error -> error + end + rescue + error in [Postgrex.Error, DBConnection.ConnectionError] -> + # Handle case where database or table doesn't exist yet + Logger.warning("Failed to load devices: #{inspect(error)}. Will retry later.") + # Store empty list for now + Cache.put(@cache_name, :all_devices, []) + :error end - rescue - error in [Postgrex.Error, DBConnection.ConnectionError] -> - # Handle case where database or table doesn't exist yet - Logger.warning("Failed to load devices: #{inspect(error)}. Will retry later.") - # Store empty list for now - Cache.put(@cache_name, :all_devices, []) - :error end end diff --git a/lib/aprsme/encoding_utils.ex b/lib/aprsme/encoding_utils.ex index 2d2e41e..f531f61 100644 --- a/lib/aprsme/encoding_utils.ex +++ b/lib/aprsme/encoding_utils.ex @@ -341,9 +341,9 @@ defmodule Aprsme.EncodingUtils do ## Examples iex> Aprsme.EncodingUtils.encoding_info("Hello") - %{valid_utf8: true, byte_count: 5, char_count: 5} + %{valid_utf8: true, byte_count: 5, char_count: 5, invalid_at: nil} iex> Aprsme.EncodingUtils.encoding_info(<<72, 101, 211, 108, 111>>) - %{valid_utf8: false, byte_count: 5, invalid_at: 2} + %{valid_utf8: false, byte_count: 5, char_count: nil, invalid_at: 2} """ @spec encoding_info(binary()) :: map() def encoding_info(binary) when is_binary(binary) do diff --git a/lib/aprsme/streaming_packets_pubsub.ex b/lib/aprsme/streaming_packets_pubsub.ex index 9b283b7..8924b12 100644 --- a/lib/aprsme/streaming_packets_pubsub.ex +++ b/lib/aprsme/streaming_packets_pubsub.ex @@ -115,18 +115,28 @@ defmodule Aprsme.StreamingPacketsPubSub do ]) # Send to matching subscribers using BroadcastTaskSupervisor + # Collect dead pids to clean up + server_pid = self() + Aprsme.BroadcastTaskSupervisor.async_execute(fn -> - subscribers - |> Stream.filter(fn {_pid, bounds} -> packet_in_bounds?(lat, lon, bounds) end) - |> Enum.each(fn {pid, _bounds} -> - # Only send if process is alive - if Process.alive?(pid) do - send(pid, {:streaming_packet, packet}) - else - # Clean up dead subscriber - :ets.delete(@table_name, pid) - end - end) + dead_pids = + subscribers + |> Stream.filter(fn {_pid, bounds} -> packet_in_bounds?(lat, lon, bounds) end) + |> Enum.reduce([], fn {pid, _bounds}, acc -> + # Only send if process is alive + if Process.alive?(pid) do + send(pid, {:streaming_packet, packet}) + acc + else + # Collect dead pid for cleanup + [pid | acc] + end + end) + + # Send dead pids back to GenServer for cleanup + if dead_pids != [] do + send(server_pid, {:cleanup_dead_subscribers, dead_pids}) + end end) end @@ -140,6 +150,16 @@ defmodule Aprsme.StreamingPacketsPubSub do {:noreply, state} end + @impl true + def handle_info({:cleanup_dead_subscribers, pids}, state) do + # Clean up dead subscribers from the GenServer process + Enum.each(pids, fn pid -> + :ets.delete(@table_name, pid) + end) + + {:noreply, state} + end + # Private functions defp valid_bounds?(%{north: n, south: s, east: e, west: w}) do diff --git a/priv/repo/migrations/20250726215048_add_optimized_indexes.exs b/priv/repo/migrations/20250726215048_add_optimized_indexes.exs index 50888c7..db61b1c 100644 --- a/priv/repo/migrations/20250726215048_add_optimized_indexes.exs +++ b/priv/repo/migrations/20250726215048_add_optimized_indexes.exs @@ -3,9 +3,6 @@ defmodule Aprsme.Repo.Migrations.AddOptimizedIndexes do @disable_ddl_transaction true @disable_migration_lock true - # Increase timeout for large table operations - @migration_timeout :timer.minutes(30) - def up do # Set statement timeout for this migration session execute "SET statement_timeout = '30min';"