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;
}