From 01570a16215d9c174abcca6e27394b8227273bfb Mon Sep 17 00:00:00 2001 From: Chomp Date: Mon, 8 Sep 2025 10:58:08 +0100 Subject: [PATCH] Improved locking inside `RagfairRequiredItemsService` Comment and name improvements --- .../Servers/RagfairServer.cs | 2 +- .../Services/RagfairRequiredItemsService.cs | 45 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Servers/RagfairServer.cs b/Libraries/SPTarkov.Server.Core/Servers/RagfairServer.cs index d5486169..159085aa 100644 --- a/Libraries/SPTarkov.Server.Core/Servers/RagfairServer.cs +++ b/Libraries/SPTarkov.Server.Core/Servers/RagfairServer.cs @@ -40,7 +40,7 @@ public class RagfairServer( ProcessExpiredFleaOffers(); // Flag data as stale and in need of regeneration - ragfairRequiredItemsService.CacheIsStale(); + ragfairRequiredItemsService.InvalidateCache(); } protected void RefreshTraderOffers() diff --git a/Libraries/SPTarkov.Server.Core/Services/RagfairRequiredItemsService.cs b/Libraries/SPTarkov.Server.Core/Services/RagfairRequiredItemsService.cs index 503b22e9..2d6fa613 100644 --- a/Libraries/SPTarkov.Server.Core/Services/RagfairRequiredItemsService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/RagfairRequiredItemsService.cs @@ -8,17 +8,18 @@ namespace SPTarkov.Server.Core.Services; [Injectable(InjectionType.Singleton)] public class RagfairRequiredItemsService(RagfairOfferService ragfairOfferService, PaymentHelper paymentHelper) { - private bool _cacheIsStale = true; + private readonly Lock _createCacheLock = new(); + private volatile bool _cacheIsStale = true; /// - /// Key = tpl + /// Key = tpl, Value = offerIds /// - protected readonly ConcurrentDictionary> RequiredItemsCache = new(); + private ConcurrentDictionary> _requiredItemsCache = new(); /// /// Empty hashset to be returned when no keys found by GetRequiredOffersById (reduces memory allocations) /// - protected readonly IReadOnlySet EmptyOfferIdSet = new HashSet(); + private readonly IReadOnlySet _emptyOfferIdSet = new HashSet(); /// /// Get the offerId of offers that require the supplied tpl @@ -29,10 +30,18 @@ public class RagfairRequiredItemsService(RagfairOfferService ragfairOfferService { if (_cacheIsStale) { - BuildRequiredItemTable(); + // Lock to prevent 2 threads building table + lock (_createCacheLock) + { + // Second check in the event another thread just built table + if (_cacheIsStale) + { + BuildRequiredItemTable(); + } + } } - return RequiredItemsCache.TryGetValue(tpl, out var offerIds) ? offerIds : EmptyOfferIdSet; + return _requiredItemsCache.TryGetValue(tpl, out var offerIds) ? offerIds : _emptyOfferIdSet; } /// @@ -40,11 +49,16 @@ public class RagfairRequiredItemsService(RagfairOfferService ragfairOfferService /// public void BuildRequiredItemTable() { - Clear(); + ConcurrentDictionary> newCache = new(); foreach (var offer in ragfairOfferService.GetOffers()) { - foreach (var requirement in offer.Requirements ?? []) + if (offer.Requirements is null) + { + continue; + } + + foreach (var requirement in offer.Requirements) { // Skip offers for currency, we only need barter offers as this cache is used by `GetOffersThatRequireItem` if (paymentHelper.IsMoneyTpl(requirement.TemplateId)) @@ -53,29 +67,24 @@ public class RagfairRequiredItemsService(RagfairOfferService ragfairOfferService } // Ensure cache has Hashset init for this tpl - var offerIds = RequiredItemsCache.GetOrAdd(requirement.TemplateId, _ => []); + var offerIds = newCache.GetOrAdd(requirement.TemplateId, _ => []); // Add offer id against the tpl key offerIds.Add(offer.Id); } } + // Replace cache in one go + _requiredItemsCache = newCache; + // Cache is now fresh _cacheIsStale = false; } - /// - /// Clear all data from cache - /// - public void Clear() - { - RequiredItemsCache.Clear(); - } - /// /// Flag the cache as stale /// - public void CacheIsStale() + public void InvalidateCache() { _cacheIsStale = true; }