Skip to content

Commit aefdf90

Browse files
authored
Add test case for concurrent callers throwing in AsyncAtomicFactory.GetValueAsync (#687)
1 parent 5b2d64a commit aefdf90

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

BitFaster.Caching.UnitTests/Atomic/AsyncAtomicFactoryTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,48 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
114114
winnerCount.Should().Be(1);
115115
}
116116

117+
[Fact]
118+
public async Task WhenCallersRunConcurrentlyWithFailureSameExceptionIsPropagated()
119+
{
120+
var enter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
121+
var resume = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
122+
123+
var atomicFactory = new AsyncAtomicFactory<int, int>();
124+
125+
var first = atomicFactory.GetValueAsync(1, async k =>
126+
{
127+
enter.SetResult(true);
128+
await resume.Task;
129+
130+
throw new ArithmeticException("1");
131+
}).AsTask();
132+
133+
var second = atomicFactory.GetValueAsync(1, async k =>
134+
{
135+
enter.SetResult(true);
136+
await resume.Task;
137+
138+
throw new InvalidOperationException("2");
139+
}).AsTask();
140+
141+
await enter.Task;
142+
resume.SetResult(true);
143+
144+
// Both tasks will throw, but the first one to complete will propagate its exception
145+
// Both exceptions should be the same. If they are not, there will be an aggregate exception.
146+
try
147+
{
148+
await Task.WhenAll(first, second)
149+
.TimeoutAfter(TimeSpan.FromSeconds(5), "Tasks did not complete within the expected time. Exceptions are not propagated between callers correctly.");
150+
}
151+
catch (ArithmeticException)
152+
{
153+
}
154+
catch (InvalidOperationException)
155+
{
156+
}
157+
}
158+
117159
[Fact]
118160
public void WhenValueNotCreatedHashCodeIsZero()
119161
{

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryAsyncCacheTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public void WhenRemovedEventHandlerIsRegisteredItIsFired()
9292
}
9393

9494
// backcompat: remove conditional compile
95-
#if NETCOREAPP3_0_OR_GREATER
95+
#if NET
9696
[Fact]
9797
public void WhenUpdatedEventHandlerIsRegisteredItIsFired()
9898
{
@@ -259,7 +259,7 @@ public async Task WhenFactoryThrowsEmptyKeyIsNotEnumerable()
259259
}
260260

261261
// backcompat: remove conditional compile
262-
#if NETCOREAPP3_0_OR_GREATER
262+
#if NET
263263
[Fact]
264264
public void WhenRemovedValueIsReturned()
265265
{

BitFaster.Caching.UnitTests/Atomic/ScopedAsyncAtomicFactoryTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,49 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
156156
winnerCount.Should().Be(1);
157157
}
158158

159+
160+
[Fact]
161+
public async Task WhenCallersRunConcurrentlyWithFailureSameExceptionIsPropagated()
162+
{
163+
var enter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
164+
var resume = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
165+
166+
var atomicFactory = new ScopedAsyncAtomicFactory<int, IntHolder>();
167+
168+
var first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
169+
{
170+
enter.SetResult(true);
171+
await resume.Task;
172+
173+
throw new ArithmeticException("1");
174+
}).AsTask(); ;
175+
176+
var second = atomicFactory.TryCreateLifetimeAsync(1, async k =>
177+
{
178+
enter.SetResult(true);
179+
await resume.Task;
180+
181+
throw new InvalidOperationException("2");
182+
}).AsTask();
183+
184+
await enter.Task;
185+
resume.SetResult(true);
186+
187+
// Both tasks will throw, but the first one to complete will propagate its exception
188+
// Both exceptions should be the same. If they are not, there will be an aggregate exception.
189+
try
190+
{
191+
await Task.WhenAll(first, second)
192+
.TimeoutAfter(TimeSpan.FromSeconds(5), "Tasks did not complete within the expected time. Exceptions are not propagated between callers correctly.");
193+
}
194+
catch (ArithmeticException)
195+
{
196+
}
197+
catch (InvalidOperationException)
198+
{
199+
}
200+
}
201+
159202
[Fact]
160203
public async Task WhenDisposedWhileInitResultIsDisposed()
161204
{

0 commit comments

Comments
 (0)