From 92585ae34facd2c24f94bd9798a2c46e410c04a7 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 18 Jul 2025 12:27:46 -0500 Subject: [PATCH] Fix race condition when zooming map quickly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Increase debounce time from 300ms to 400ms for bounds updates - Cancel in-progress historical loading when new bounds are scheduled - Add stale update detection to skip outdated bounds updates - Clear timer reference and pending bounds when processing updates - Add debug logging for cancelled loads and stale updates This prevents overlapping historical data loads during rapid zoom changes, eliminating duplicate packets and improving performance. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../live/map_live/historical_loader.ex | 5 ++ lib/aprsme_web/live/map_live/index.ex | 48 ++++++++++++------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/aprsme_web/live/map_live/historical_loader.ex b/lib/aprsme_web/live/map_live/historical_loader.ex index 9066c86..89c6550 100644 --- a/lib/aprsme_web/live/map_live/historical_loader.ex +++ b/lib/aprsme_web/live/map_live/historical_loader.ex @@ -38,6 +38,11 @@ defmodule AprsmeWeb.MapLive.HistoricalLoader do "start_progressive_historical_loading called with zoom: #{socket.assigns.map_zoom}, bounds: #{inspect(socket.assigns.map_bounds)}" ) + # If we're already loading, just update the generation to cancel old requests + if socket.assigns[:historical_loading] do + Logger.debug("Already loading historical data, cancelling previous load") + end + # Increment generation to invalidate any in-flight loads new_generation = socket.assigns.loading_generation + 1 diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index 0b02206..2d3e69d 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -639,24 +639,33 @@ defmodule AprsmeWeb.MapLive.Index do defp handle_info_process_bounds_update(map_bounds, socket) do require Logger - # Check if we need to process this bounds update - should_process = - !socket.assigns[:initial_bounds_loaded] or - socket.assigns[:needs_initial_historical_load] or - not BoundsUtils.compare_bounds(map_bounds, socket.assigns.map_bounds) - - Logger.debug( - "handle_info_process_bounds_update - should_process: #{should_process}, initial_bounds_loaded: #{socket.assigns[:initial_bounds_loaded]}, needs_initial_historical_load: #{socket.assigns[:needs_initial_historical_load]}" - ) - - if should_process do - Logger.debug("Processing bounds update: #{inspect(map_bounds)}") - socket = process_bounds_update(map_bounds, socket) - socket = assign(socket, initial_bounds_loaded: true) + # Check if this is a stale update (newer bounds have been scheduled) + if socket.assigns[:pending_bounds] && socket.assigns.pending_bounds != map_bounds do + Logger.debug("Skipping stale bounds update, newer bounds pending") {:noreply, socket} else - Logger.debug("Skipping bounds update - no change detected") - {:noreply, socket} + # Clear the timer reference since we're processing this update + socket = assign(socket, bounds_update_timer: nil, pending_bounds: nil) + + # Check if we need to process this bounds update + should_process = + !socket.assigns[:initial_bounds_loaded] or + socket.assigns[:needs_initial_historical_load] or + not BoundsUtils.compare_bounds(map_bounds, socket.assigns.map_bounds) + + Logger.debug( + "handle_info_process_bounds_update - should_process: #{should_process}, initial_bounds_loaded: #{socket.assigns[:initial_bounds_loaded]}, needs_initial_historical_load: #{socket.assigns[:needs_initial_historical_load]}" + ) + + if should_process do + Logger.debug("Processing bounds update: #{inspect(map_bounds)}") + socket = process_bounds_update(map_bounds, socket) + socket = assign(socket, initial_bounds_loaded: true) + {:noreply, socket} + else + Logger.debug("Skipping bounds update - no change detected") + {:noreply, socket} + end end end @@ -1355,11 +1364,16 @@ defmodule AprsmeWeb.MapLive.Index do end defp schedule_bounds_update(map_bounds, socket) do + # Cancel existing timer if present if socket.assigns[:bounds_update_timer] do Process.cancel_timer(socket.assigns.bounds_update_timer) end - timer_ref = Process.send_after(self(), {:process_bounds_update, map_bounds}, 300) + # Cancel any in-progress historical loading to prevent race conditions + socket = HistoricalLoader.cancel_pending_loads(socket) + + # Increase debounce time slightly for better stability during rapid zooming + timer_ref = Process.send_after(self(), {:process_bounds_update, map_bounds}, 400) socket = assign(socket, bounds_update_timer: timer_ref, pending_bounds: map_bounds) {:noreply, socket} end