diff --git a/BitFaster.Caching.UnitTests/Atomic/AtomicFactoryScopedCacheSoakTests.cs b/BitFaster.Caching.UnitTests/Atomic/AtomicFactoryScopedCacheSoakTests.cs new file mode 100644 index 00000000..1ffca0c7 --- /dev/null +++ b/BitFaster.Caching.UnitTests/Atomic/AtomicFactoryScopedCacheSoakTests.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BitFaster.Caching.Atomic; +using BitFaster.Caching.Lru; +using FluentAssertions; +using Moq; +using Xunit; + +namespace BitFaster.Caching.UnitTests.Atomic +{ + [Collection("Soak")] + public class AtomicFactoryScopedCacheSoakTests + { + private const int capacity = 6; + private const int threadCount = 4; + private const int soakIterations = 10; + private const int loopIterations = 100_000; + + [Theory] + [Repeat(soakIterations)] + public async Task ScopedGetOrAddAlternateLifetimeIsAlwaysAlive(int _) + { + var cache = new AtomicFactoryScopedCache(new ConcurrentLru>(1, capacity, EqualityComparer.Default)); + + var run = Threaded.Run(threadCount, _ => + { + var key = new char[8]; + + for (int i = 0; i < loopIterations; i++) + { + using (var lifetime = cache.ScopedGetOrAdd(i, k => { return new Scoped(new Disposable(k)); })) + { + lifetime.Value.IsDisposed.Should().BeFalse($"ref count {lifetime.ReferenceCount}"); + } + } + }); + + await run; + } + } +} diff --git a/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs b/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs index 839b51a1..e82e13ba 100644 --- a/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs +++ b/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs @@ -132,7 +132,7 @@ private async ValueTask InitializeScopeAsync(K key, TFactory valueFact /// public void Dispose() { - var init = initializer; + var init = Volatile.Read(ref initializer); if (init != null && init.TryGetScope(out var disposeScope)) { diff --git a/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs b/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs index 55661e99..ef1765bb 100644 --- a/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs +++ b/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Threading; namespace BitFaster.Caching.Atomic @@ -134,7 +135,7 @@ public bool TryCreateLifetime(K key, TFactory valueFactory, [MaybeNull private void InitializeScope(K key, TFactory valueFactory) where TFactory : struct, IValueFactory> { - var init = initializer; + var init = Volatile.Read(ref initializer); if (init != null) { @@ -149,7 +150,7 @@ private void InitializeScope(K key, TFactory valueFactory) where TFact /// public void Dispose() { - var init = initializer; + var init = Volatile.Read(ref initializer); if (init != null) {