From 3a55d120d0048db5a2880b81566169b24324125e Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 5 May 2026 17:49:58 -0700 Subject: [PATCH 1/3] define conc level by CPU count --- BitFaster.Caching/Lru/Defaults.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/BitFaster.Caching/Lru/Defaults.cs b/BitFaster.Caching/Lru/Defaults.cs index 79624d3c..45b63064 100644 --- a/BitFaster.Caching/Lru/Defaults.cs +++ b/BitFaster.Caching/Lru/Defaults.cs @@ -4,11 +4,6 @@ namespace BitFaster.Caching.Lru { internal static class Defaults { -#if NET8_0_OR_GREATER - // Note that on .net8+, -1 indicates the default concurrency level - public static int ConcurrencyLevel => -1; -#else public static int ConcurrencyLevel => Environment.ProcessorCount; -#endif } } From dd62a7fd11abc0a4a53bde392b6290fd47ae8234 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 16 Jun 2026 10:20:01 -0700 Subject: [PATCH 2/3] handle --- BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs | 11 ++++++++++- BitFaster.Caching/Lfu/ConcurrentLfuCore.cs | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index be08bbf5..e6c796be 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs @@ -56,13 +56,22 @@ public void WhenCapacityIsValidCacheIsCreated() } [Fact] - public void WhenConcurrencyIsLessThan1CtorThrows() + public void WhenConcurrencyIsZeroCtorThrows() { Action constructor = () => { var x = new ConcurrentLfu(0, 20, new ForegroundScheduler(), EqualityComparer.Default); }; constructor.Should().Throw(); } +#if NET8_0_OR_GREATER + // -1 is default conc level on .NET 8+. This can cause invalid striped buffer size without explicit handling. + [Fact] + public void WhenConcurrencyIsNegativeOneCacheIsCreated() + { + var x = new ConcurrentLfu(-1, 20, new ForegroundScheduler(), EqualityComparer.Default); + } +#endif + [Fact] public void DefaultSchedulerIsThreadPool() { diff --git a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs index 02822287..923cb8fa 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs @@ -11,6 +11,8 @@ using BitFaster.Caching.Buffers; using BitFaster.Caching.Counters; using BitFaster.Caching.Scheduler; +using BitFaster.Caching.Lru; + #if DEBUG using System.Text; @@ -96,6 +98,14 @@ public ConcurrentLfuCore(int concurrencyLevel, int capacity, IScheduler schedule int dictionaryCapacity = ConcurrentDictionarySize.Estimate(capacity); this.dictionary = new(concurrencyLevel, dictionaryCapacity, comparer); + // On .NET 8+, -1 can be used for default conc level. concurrencyLevel is guarded by the dictionary ctor that will not throw in this case. +#if NET8_0_OR_GREATER + if (concurrencyLevel == -1) + { + concurrencyLevel = Defaults.ConcurrencyLevel; + } +#endif + // cap concurrency at proc count * 2 int readStripes = Math.Min(BitOps.CeilingPowerOfTwo(concurrencyLevel), BitOps.CeilingPowerOfTwo(Environment.ProcessorCount * 2)); this.readBuffer = new(readStripes, DefaultBufferSize); From 3a4b560ecfeb75c31b242aadd87618647a4cfaa2 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 16 Jun 2026 13:56:11 -0700 Subject: [PATCH 3/3] better test --- BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index e6c796be..30bc207b 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using BitFaster.Caching.Buffers; using BitFaster.Caching.Lfu; +using BitFaster.Caching.Lru; using BitFaster.Caching.Scheduler; using BitFaster.Caching.UnitTests.Lru; using FluentAssertions; @@ -66,9 +67,10 @@ public void WhenConcurrencyIsZeroCtorThrows() #if NET8_0_OR_GREATER // -1 is default conc level on .NET 8+. This can cause invalid striped buffer size without explicit handling. [Fact] - public void WhenConcurrencyIsNegativeOneCacheIsCreated() + public void WhenConcurrencyIsNegativeOneReadBufferIsCorrectSize() { var x = new ConcurrentLfu(-1, 20, new ForegroundScheduler(), EqualityComparer.Default); + x.Core.readBuffer.Capacity.Should().Be(BitOps.CeilingPowerOfTwo(Defaults.ConcurrencyLevel) * 128); } #endif