@@ -1757,6 +1757,24 @@ async function handlespot(spot, spot_source = "cluster") {
17571757 }
17581758 }
17591759
1760+ // Check for a different callsign on the same frequency
1761+ const freqSpot = frequencyIndex . get ( dxSpot . frequency ) ;
1762+ if ( freqSpot && generateSpotKey ( freqSpot ) !== spotKey ) {
1763+ const newTimestamp = Date . parse ( dxSpot . when ) ;
1764+ const freqTimestamp = Date . parse ( freqSpot . when ) ;
1765+ if ( newTimestamp > freqTimestamp ) {
1766+ // New spot is newer - evict the old one from the same QRG
1767+ const index = spots . indexOf ( freqSpot ) ;
1768+ if ( index !== - 1 ) {
1769+ spots . splice ( index , 1 ) ;
1770+ }
1771+ removeFromIndexes ( freqSpot ) ;
1772+ } else {
1773+ // Existing spot on this QRG is newer - discard new spot
1774+ return ;
1775+ }
1776+ }
1777+
17601778 // Insert spot in sorted position (by timestamp) using binary search
17611779 // This maintains the array sorted at all times, making eviction O(1)
17621780 const insertIndex = findInsertionIndex ( spots , dxSpot ) ;
@@ -1770,70 +1788,26 @@ async function handlespot(spot, spot_source = "cluster") {
17701788 broadcastSpot ( dxSpot ) ;
17711789 }
17721790
1773- // Empty out spots if maximum cache is reached
1774- // Two-phase eviction: 1) Remove expired RBN spots, 2) LRU eviction if still needed
1791+ // Empty out spots if maximum cache is reached - LRU eviction
17751792 if ( spots . length >= config . maxcache ) {
1776- // Phase 1: Remove expired RBN spots (they're stale after 5 minutes)
1777- const expiredCount = cleanupExpiredRBN ( ) ;
1778- if ( expiredCount > 0 ) {
1779- console . log ( `[Core] Cache full: removed ${ expiredCount } expired RBN spots, now ${ spots . length } spots` ) ;
1780- } // Phase 2: If still full after RBN cleanup, do LRU eviction
1781- if ( spots . length >= config . maxcache ) {
1782- const batchSize = Math . max ( Math . floor ( config . maxcache * 0.1 ) , 10 ) ; // Remove at least 10 spots
1783-
1784- // Atomic removal: slice creates new array without oldest spots
1785- // This prevents race conditions during API reads
1786- const removedSpots = spots . slice ( 0 , batchSize ) ;
1787- spots = spots . slice ( batchSize ) ;
1788-
1789- // Clean up indexes
1790- removedSpots . forEach ( spot => removeFromIndexes ( spot ) ) ;
1791-
1792- console . log ( `[Core] Cache still full (${ config . maxcache } ): removed ${ batchSize } oldest spots, now ${ spots . length } spots` ) ;
1793- }
1793+ const batchSize = Math . max ( Math . floor ( config . maxcache * 0.1 ) , 10 ) ; // Remove at least 10 spots
1794+
1795+ // Atomic removal: slice creates new array without oldest spots
1796+ // This prevents race conditions during API reads
1797+ const removedSpots = spots . slice ( 0 , batchSize ) ;
1798+ spots = spots . slice ( batchSize ) ;
1799+
1800+ // Clean up indexes
1801+ removedSpots . forEach ( spot => removeFromIndexes ( spot ) ) ;
1802+
1803+ console . log ( `[Core] Cache full (${ config . maxcache } ): removed ${ batchSize } oldest spots, now ${ spots . length } spots` ) ;
17941804 }
17951805
17961806 } catch ( e ) {
17971807 console . error ( "[Core] Error processing spot:" , e ) ;
17981808 }
17991809}
18001810
1801- /**
1802- * Removes expired RBN spots from cache
1803- * RBN spots older than RBN_SPOT_TIMEOUT should not be displayed
1804- * @returns {number } Number of spots removed
1805- */
1806- function cleanupExpiredRBN ( ) {
1807- // Early exit if no RBN spots exist
1808- if ( rbnSpotCount === 0 ) {
1809- return 0 ;
1810- }
1811-
1812- const now = Date . now ( ) ;
1813- const rbnMaxAge = config . rbnSpotTimeout * 60 * 1000 ; // Convert minutes to milliseconds
1814- let removedCount = 0 ;
1815-
1816- // Filter approach: create new array without expired RBN spots
1817- // This is safer for concurrent reads than splice operations
1818- const filteredSpots = spots . filter ( spot => {
1819- if ( spot . source === 'rbn' ) {
1820- const age = now - Date . parse ( spot . when ) ;
1821- if ( age > rbnMaxAge ) {
1822- removeFromIndexes ( spot ) ;
1823- removedCount ++ ;
1824- return false ; // Remove this spot
1825- }
1826- }
1827- return true ; // Keep this spot
1828- } ) ;
1829-
1830- // Atomic replacement if any spots were removed
1831- if ( removedCount > 0 ) {
1832- spots = filteredSpots ;
1833- }
1834-
1835- return removedCount ;
1836- }
18371811
18381812// -----------------------------------
18391813// Index Management Functions
0 commit comments