From 5f410bb05bdca1c7bbe263c6c3a283a2b8aec97f Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Wed, 18 Mar 2026 10:46:07 -0500 Subject: [PATCH] Fix ExternalSessionScanner cache thread safety Replace Dictionary with ConcurrentDictionary for _cache. The cache is read/written from a Timer callback (ThreadPool thread) in Scan(), and Dictionary is not thread-safe for concurrent operations. This could cause corruption or crashes during disposal or test scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- PolyPilot/Services/ExternalSessionScanner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PolyPilot/Services/ExternalSessionScanner.cs b/PolyPilot/Services/ExternalSessionScanner.cs index 8243d79c..8a754356 100644 --- a/PolyPilot/Services/ExternalSessionScanner.cs +++ b/PolyPilot/Services/ExternalSessionScanner.cs @@ -22,7 +22,7 @@ public class ExternalSessionScanner : IDisposable private volatile bool _disposed; // Cache: sessionId -> (eventsFileMtime, parsedInfo) - private readonly Dictionary _cache = new(); + private readonly System.Collections.Concurrent.ConcurrentDictionary _cache = new(); private static readonly TimeSpan ActiveThreshold = TimeSpan.FromMinutes(2); private static readonly TimeSpan IdleThreshold = TimeSpan.FromHours(4); @@ -283,7 +283,7 @@ internal void Scan() // Evict cache entries for sessions no longer live var staleKeys = _cache.Keys.Where(k => !seenIds.Contains(k)).ToList(); - foreach (var k in staleKeys) _cache.Remove(k); + foreach (var k in staleKeys) _cache.TryRemove(k, out _); // Sort by recency newSessions.Sort((a, b) => b.LastEventTime.CompareTo(a.LastEventTime));