diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index be08bbf5..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; @@ -56,13 +57,23 @@ 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 WhenConcurrencyIsNegativeOneReadBufferIsCorrectSize() + { + var x = new ConcurrentLfu(-1, 20, new ForegroundScheduler(), EqualityComparer.Default); + x.Core.readBuffer.Capacity.Should().Be(BitOps.CeilingPowerOfTwo(Defaults.ConcurrencyLevel) * 128); + } +#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); 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 } }