From ce1cd1597834abd11b0c4381f5d5ffde5cc5b155 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 11 Aug 2026 22:03:50 -0400 Subject: [PATCH] Fix locking issues with item dictionary implementation --- .../ItemDictionary.cs | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/SabreTools.Metadata.DatFiles/ItemDictionary.cs b/SabreTools.Metadata.DatFiles/ItemDictionary.cs index 29973ee0..f2f64b40 100644 --- a/SabreTools.Metadata.DatFiles/ItemDictionary.cs +++ b/SabreTools.Metadata.DatFiles/ItemDictionary.cs @@ -40,6 +40,15 @@ namespace SabreTools.Metadata.DatFiles private readonly Dictionary?> _items = []; #endif + /// + /// Internal dictionary for the class + /// +#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER + private readonly ConcurrentDictionary _itemsLocks = []; +#else + private readonly Dictionary _itemsLocks = []; +#endif + /// /// Logging object /// @@ -191,7 +200,14 @@ namespace SabreTools.Metadata.DatFiles // If only adding statistics, we add an empty key for games and then just item stats if (statsOnly) { - EnsureBucketingKey(key); + // Add an empty key +#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER + _items.GetOrAdd(key, []); +#else + if (!_items.ContainsKey(key)) + _items[key] = []; +#endif + DatStatistics.AddItemStatistics(item); } else @@ -294,7 +310,16 @@ namespace SabreTools.Metadata.DatFiles public bool RemoveItem(string key, DatItem value, int index) { // Explicit lock for some weird corner cases - lock (key) +#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER + object lockObj = _itemsLocks.GetOrAdd(key, _ => new object()); +#else + if (!_itemsLocks.ContainsKey(key)) + _itemsLocks[key] = new object(); + + object lockObj = _itemsLocks[key]; +#endif + + lock (lockObj) { // If the key doesn't exist, return #if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER @@ -338,10 +363,24 @@ namespace SabreTools.Metadata.DatFiles internal void AddItem(string key, DatItem value) { // Explicit lock for some weird corner cases - lock (key) +#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER + object lockObj = _itemsLocks.GetOrAdd(key, _ => new object()); +#else + if (!_itemsLocks.ContainsKey(key)) + _itemsLocks[key] = new object(); + + object lockObj = _itemsLocks[key]; +#endif + + lock (lockObj) { // Ensure the key exists - EnsureBucketingKey(key); +#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER + _items.GetOrAdd(key, []); +#else + if (!_items.ContainsKey(key)) + _items[key] = []; +#endif // If item is null, don't add it if (value is null) @@ -640,21 +679,6 @@ namespace SabreTools.Metadata.DatFiles return roms.FindIndex(datItem.Equals) > -1; } - /// - /// Ensure the key exists in the items dictionary - /// - /// Key to ensure - private void EnsureBucketingKey(string key) - { - // If the key is missing from the dictionary, add it -#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER - _items.GetOrAdd(key, []); -#else - if (!_items.ContainsKey(key)) - _items[key] = []; -#endif - } - /// /// Get the highest-order Field value that represents the statistics ///