From 83e066c1e237c16212b3758f8de6cb525e6a8308 Mon Sep 17 00:00:00 2001 From: Takashi Hashida Date: Mon, 23 Mar 2026 09:30:47 -0700 Subject: [PATCH] Fix: protect static cache in RaceDataManager with a lock to prevent race conditions CachedHoldingInformation is a static field accessed from both the UI thread and background Task.Run threads without synchronization, creating a data race. Add a dedicated static lock object (_cacheLock) and mark the field volatile so that reads and writes to the cache are properly serialized across threads. Co-Authored-By: Claude Sonnet 4.6 --- Domain/RaceDataManager.cs | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Domain/RaceDataManager.cs b/Domain/RaceDataManager.cs index 2725b2f..2f7e753 100644 --- a/Domain/RaceDataManager.cs +++ b/Domain/RaceDataManager.cs @@ -12,23 +12,32 @@ namespace HorseRacingAutoPurchaser.Domain { public class RaceDataManager { - private static HoldingInformation CachedHoldingInformation { get; set; } + private static volatile HoldingInformation CachedHoldingInformation; + private static readonly object _cacheLock = new object(); - public static void ResetCache() - { - CachedHoldingInformation = null; + public static void ResetCache() + { + lock (_cacheLock) + { + CachedHoldingInformation = null; + } } public static IEnumerable GetRaceDataOfDay(DateTime date, bool useHoldingInformationCache = false) { var holdingInformationRepository = new HoldingInformationRepository(); HoldingInformation currentHoldingInformation; - if (useHoldingInformationCache && CachedHoldingInformation != null) { - currentHoldingInformation = CachedHoldingInformation; - } - else { - currentHoldingInformation = holdingInformationRepository.ReadAll(); - CachedHoldingInformation = currentHoldingInformation; + lock (_cacheLock) + { + if (useHoldingInformationCache && CachedHoldingInformation != null) + { + currentHoldingInformation = CachedHoldingInformation; + } + else + { + currentHoldingInformation = holdingInformationRepository.ReadAll(); + CachedHoldingInformation = currentHoldingInformation; + } } var holdingData = new List(); @@ -58,8 +67,8 @@ public static IEnumerable GetRaceDataOfDay(DateTime date, bool useHold public static IEnumerable GetAndStoreRaceDataOfDay(DateTime date, Scraper scraper) { var holdingInformationRepository = new HoldingInformationRepository(); - var currentHoldingInformation = holdingInformationRepository.ReadAll() ?? new HoldingInformation(new List()); - + var currentHoldingInformation = holdingInformationRepository.ReadAll() ?? new HoldingInformation(new List()); + if (!currentHoldingInformation.HoldingData.Any(_ => _.HeldDate.Date == date.Date)) {