From 91f783f80fd53b80fc0e29ddcbc56ab67e3a73af Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Thu, 2 Oct 2025 16:53:30 +0200 Subject: [PATCH] Handled compact being called multiple times due to eventual consistency of the cache count. --- .../Library/Main/Operation/Restore/BlockManager.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs index cbaebf0aa..ba363133a 100644 --- a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs +++ b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs @@ -77,6 +77,11 @@ namespace Duplicati.Library.Main.Operation.Restore /// private int m_block_cache_count = 0; /// + /// Flag indicating if the cache is currently being compacted. Used to avoid triggering multiple compactions as they are expensive. + /// Multiple triggers can occur as the eviction callback is launched in a task, so the count can be above the max for a short while. + /// + private int m_block_cache_compacting = 0; + /// /// The dictionary holding the `Task` for each block request in flight. /// private readonly ConcurrentDictionary Task)> m_waiters = []; @@ -157,6 +162,8 @@ namespace Duplicati.Library.Main.Operation.Restore } Interlocked.Decrement(ref m_block_cache_count); Logging.Log.WriteExplicitMessage(LOGTAG, "CacheEvictCallback", "Evicted block {0} from cache", key); + if (reason is EvictionReason.Capacity) + Interlocked.Exchange(ref m_block_cache_compacting, 0); }); m_readers = readers; sw_cacheevict = options.InternalProfiling ? new() : null; @@ -348,7 +355,7 @@ namespace Duplicati.Library.Main.Operation.Restore sw_set_wake_get?.Stop(); sw_cacheevict?.Start(); - if (m_block_cache_count > m_options.RestoreCacheMax) + if (m_block_cache_count > m_options.RestoreCacheMax && Interlocked.CompareExchange(ref m_block_cache_compacting, 1, 0) == 0) { m_block_cache.Compact(m_options.RestoreCacheMax == 0 ? 1.0 : m_options.RestoreCacheEvict); } @@ -405,9 +412,6 @@ namespace Duplicati.Library.Main.Operation.Restore Logging.Log.WriteErrorMessage(LOGTAG, "BlockCacheMismatch", null, $"Block counts in cache ({m_blockcount.Count}): {string.Join(", ", m_blockcount.Select(x => x.Value))}"); } - if (m_block_cache.Count != m_block_cache_count) - Logging.Log.WriteErrorMessage(LOGTAG, "BlockCountMismatch", null, "The secondary counter doesn't match the actual count {0} != {1} (max size is {2})", m_block_cache_count, m_block_cache.Count, m_options.RestoreCacheMax); - if (m_options.InternalProfiling) { Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Sleepable dictionary - CheckCounts: {sw_checkcounts!.ElapsedMilliseconds}ms, Get wait: {sw_get_wait!.ElapsedMilliseconds}ms, Set set: {sw_set_set!.ElapsedMilliseconds}ms, Set wake get: {sw_set_wake_get!.ElapsedMilliseconds}ms, Set wake set: {sw_set_wake_set!.ElapsedMilliseconds}ms, Cache evict: {sw_cacheevict!.ElapsedMilliseconds}ms");