Skip to content

Commit 979ee61

Browse files
authored
soak test ScopedAtomicFactory (#427)
* soak * simplify * test dispose * dispose ---------
1 parent e21bd63 commit 979ee61

File tree

2 files changed

+82
-12
lines changed

2 files changed

+82
-12
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System.Collections.Concurrent;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using BitFaster.Caching.Atomic;
5+
using FluentAssertions;
6+
using Xunit;
7+
8+
namespace BitFaster.Caching.UnitTests.Atomic
9+
{
10+
[Collection("Soak")]
11+
public class ScopedAtomicFactorySoakTests
12+
{
13+
[Fact]
14+
public async Task WhenGetOrAddIsConcurrentValuesCreatedAtomically()
15+
{
16+
const int threads = 4;
17+
const int items = 1024;
18+
var dictionary = new ConcurrentDictionary<int, ScopedAtomicFactory<int, Disposable>>(concurrencyLevel: threads, capacity: items);
19+
var counters = new int[threads];
20+
21+
await Threaded.Run(threads, (r) =>
22+
{
23+
for (int i = 0; i < items; i++)
24+
{
25+
while (true)
26+
{
27+
var scoped = dictionary.GetOrAdd(i, k => new ScopedAtomicFactory<int, Disposable>());
28+
if (scoped.TryCreateLifetime(i, k => { counters[r]++; return new Scoped<Disposable>(new Disposable(k)); }, out var lifetime))
29+
{
30+
using (lifetime)
31+
{
32+
lifetime.Value.IsDisposed.Should().BeFalse();
33+
}
34+
35+
break;
36+
}
37+
}
38+
39+
}
40+
});
41+
42+
counters.Sum(x => x).Should().Be(items);
43+
}
44+
45+
[Fact]
46+
public async Task WhenGetOrAddAndDisposeIsConcurrentValuesCreatedAtomically()
47+
{
48+
const int threads = 4;
49+
const int items = 1024;
50+
var dictionary = new ConcurrentDictionary<int, ScopedAtomicFactory<int, Disposable>>(concurrencyLevel: threads, capacity: items);
51+
var counters = new int[threads];
52+
53+
await Threaded.Run(threads, (r) =>
54+
{
55+
for (int i = 0; i < items; i++)
56+
{
57+
if (dictionary.TryRemove(i, out var d))
58+
{
59+
d.Dispose();
60+
}
61+
62+
while (true)
63+
{
64+
var scoped = dictionary.GetOrAdd(i, k => new ScopedAtomicFactory<int, Disposable>());
65+
66+
if (scoped.TryCreateLifetime(i, k => { counters[r]++; return new Scoped<Disposable>(new Disposable(k)); }, out var lifetime))
67+
{
68+
using (lifetime)
69+
{
70+
lifetime.Value.IsDisposed.Should().BeFalse();
71+
}
72+
73+
break;
74+
}
75+
}
76+
77+
}
78+
});
79+
}
80+
}
81+
}

BitFaster.Caching/Atomic/ScopedAtomicFactory.cs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ private void InitializeScope<TFactory>(K key, TFactory valueFactory) where TFact
136136
if (init != null)
137137
{
138138
scope = init.CreateScope(key, valueFactory);
139-
initializer = null;
139+
Volatile.Write(ref initializer, null); // volatile write must occur after setting value
140140
}
141141
}
142142

@@ -164,11 +164,6 @@ private class Initializer
164164

165165
public Scoped<V> CreateScope<TFactory>(K key, TFactory valueFactory) where TFactory : struct, IValueFactory<K, Scoped<V>>
166166
{
167-
if (Volatile.Read(ref isInitialized))
168-
{
169-
return value;
170-
}
171-
172167
lock (syncLock)
173168
{
174169
if (Volatile.Read(ref isInitialized))
@@ -185,12 +180,6 @@ public Scoped<V> CreateScope<TFactory>(K key, TFactory valueFactory) where TFact
185180

186181
public Scoped<V> TryCreateDisposedScope()
187182
{
188-
// already exists, return it
189-
if (Volatile.Read(ref isInitialized))
190-
{
191-
return value;
192-
}
193-
194183
lock (syncLock)
195184
{
196185
if (Volatile.Read(ref isInitialized))

0 commit comments

Comments
 (0)