Skip to content

Commit f4ff573

Browse files
authored
idempotent value creation (#153)
* atom * default * ex * conc * scoped * scoped async * test scope * test scope * use scope * fix warnings * code format * atomic * success * idempotent
1 parent 617bff7 commit f4ff573

File tree

9 files changed

+961
-1
lines changed

9 files changed

+961
-1
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using BitFaster.Caching.Synchronized;
8+
using FluentAssertions;
9+
using Xunit;
10+
11+
namespace BitFaster.Caching.UnitTests.Synchronized
12+
{
13+
public class AsyncIdempotentTests
14+
{
15+
[Fact]
16+
public void DefaultCtorValueIsNotCreated()
17+
{
18+
var a = new AsyncIdempotent<int, int>();
19+
20+
a.IsValueCreated.Should().BeFalse();
21+
a.ValueIfCreated.Should().Be(0);
22+
}
23+
24+
[Fact]
25+
public void WhenValuePassedToCtorValueIsStored()
26+
{
27+
var a = new AsyncIdempotent<int, int>(1);
28+
29+
a.ValueIfCreated.Should().Be(1);
30+
a.IsValueCreated.Should().BeTrue();
31+
}
32+
33+
[Fact]
34+
public async Task WhenValueCreatedValueReturned()
35+
{
36+
var a = new AsyncIdempotent<int, int>();
37+
(await a.GetValueAsync(1, k => Task.FromResult(2))).Should().Be(2);
38+
39+
a.ValueIfCreated.Should().Be(2);
40+
a.IsValueCreated.Should().BeTrue();
41+
}
42+
43+
[Fact]
44+
public async Task WhenValueCreatedGetValueReturnsOriginalValue()
45+
{
46+
var a = new AsyncIdempotent<int, int>();
47+
await a.GetValueAsync(1, k => Task.FromResult(2));
48+
(await a.GetValueAsync(1, k => Task.FromResult(3))).Should().Be(2);
49+
}
50+
51+
[Fact]
52+
public async Task WhenValueCreateThrowsValueIsNotStored()
53+
{
54+
var a = new AsyncIdempotent<int, int>();
55+
56+
Func<Task> getOrAdd = async () => { await a.GetValueAsync(1, k => throw new ArithmeticException()); };
57+
58+
await getOrAdd.Should().ThrowAsync<ArithmeticException>();
59+
60+
(await a.GetValueAsync(1, k => Task.FromResult(3))).Should().Be(3);
61+
}
62+
63+
[Fact]
64+
public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
65+
{
66+
var enter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
67+
var resume = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
68+
69+
var idempotent = new AsyncIdempotent<int, int>();
70+
var result = 0;
71+
var winnerCount = 0;
72+
73+
Task<int> first = idempotent.GetValueAsync(1, async k =>
74+
{
75+
enter.SetResult(true);
76+
await resume.Task;
77+
78+
result = 1;
79+
Interlocked.Increment(ref winnerCount);
80+
return 1;
81+
});
82+
83+
Task<int> second = idempotent.GetValueAsync(1, async k =>
84+
{
85+
enter.SetResult(true);
86+
await resume.Task;
87+
88+
result = 2;
89+
Interlocked.Increment(ref winnerCount);
90+
return 2;
91+
});
92+
93+
await enter.Task;
94+
resume.SetResult(true);
95+
96+
(await first).Should().Be(result);
97+
(await second).Should().Be(result);
98+
99+
winnerCount.Should().Be(1);
100+
}
101+
}
102+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using BitFaster.Caching.Synchronized;
8+
using FluentAssertions;
9+
using Xunit;
10+
11+
namespace BitFaster.Caching.UnitTests.Synchronized
12+
{
13+
public class IdempotentTests
14+
{
15+
[Fact]
16+
public void DefaultCtorValueIsNotCreated()
17+
{
18+
var a = new Idempotent<int, int>();
19+
20+
a.IsValueCreated.Should().BeFalse();
21+
a.ValueIfCreated.Should().Be(0);
22+
}
23+
24+
[Fact]
25+
public void WhenValuePassedToCtorValueIsStored()
26+
{
27+
var a = new Idempotent<int, int>(1);
28+
29+
a.ValueIfCreated.Should().Be(1);
30+
a.IsValueCreated.Should().BeTrue();
31+
}
32+
33+
[Fact]
34+
public void WhenValueCreatedValueReturned()
35+
{
36+
var a = new Idempotent<int, int>();
37+
a.GetValue(1, k => 2).Should().Be(2);
38+
39+
a.ValueIfCreated.Should().Be(2);
40+
a.IsValueCreated.Should().BeTrue();
41+
}
42+
43+
[Fact]
44+
public void WhenValueCreatedGetValueReturnsOriginalValue()
45+
{
46+
var a = new Idempotent<int, int>();
47+
a.GetValue(1, k => 2);
48+
a.GetValue(1, k => 3).Should().Be(2);
49+
}
50+
51+
[Fact]
52+
public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
53+
{
54+
var enter = new ManualResetEvent(false);
55+
var resume = new ManualResetEvent(false);
56+
57+
var idempotent = new Idempotent<int, int>();
58+
var result = 0;
59+
var winnerCount = 0;
60+
61+
Task<int> first = Task.Run(() =>
62+
{
63+
return idempotent.GetValue(1, k =>
64+
{
65+
enter.Set();
66+
resume.WaitOne();
67+
68+
result = 1;
69+
Interlocked.Increment(ref winnerCount);
70+
return 1;
71+
});
72+
});
73+
74+
Task<int> second = Task.Run(() =>
75+
{
76+
return idempotent.GetValue(1, k =>
77+
{
78+
enter.Set();
79+
resume.WaitOne();
80+
81+
result = 2;
82+
Interlocked.Increment(ref winnerCount);
83+
return 2;
84+
});
85+
});
86+
87+
enter.WaitOne();
88+
resume.Set();
89+
90+
(await first).Should().Be(result);
91+
(await second).Should().Be(result);
92+
93+
winnerCount.Should().Be(1);
94+
}
95+
}
96+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using BitFaster.Caching.Synchronized;
8+
using FluentAssertions;
9+
using Xunit;
10+
11+
namespace BitFaster.Caching.UnitTests.Synchronized
12+
{
13+
public class ScopedAsyncIdempotentTests
14+
{
15+
[Fact]
16+
public async Task WhenCreateFromValueLifetimeContainsValue()
17+
{
18+
var idempotent = new ScopedAsyncIdempotent<int, IntHolder>(new IntHolder() { actualNumber = 1 });
19+
20+
(bool r, Lifetime<IntHolder> l) result = await idempotent.TryCreateLifetimeAsync(1, k =>
21+
{
22+
return Task.FromResult(new IntHolder() { actualNumber = 2 });
23+
});
24+
25+
result.r.Should().BeTrue();
26+
result.l.Value.actualNumber.Should().Be(1);
27+
}
28+
29+
[Fact]
30+
public async Task WhenScopeIsDisposedTryCreateReturnsFalse()
31+
{
32+
var idempotent = new ScopedAsyncIdempotent<int, IntHolder>(new IntHolder() { actualNumber = 1 });
33+
idempotent.Dispose();
34+
35+
(bool r, Lifetime<IntHolder> l) result = await idempotent.TryCreateLifetimeAsync(1, k =>
36+
{
37+
return Task.FromResult(new IntHolder() { actualNumber = 2 });
38+
});
39+
40+
result.r.Should().BeFalse();
41+
result.l.Should().BeNull();
42+
}
43+
44+
[Fact]
45+
public void WhenValueIsCreatedDisposeDisposesValue()
46+
{
47+
var holder = new IntHolder() { actualNumber = 2 };
48+
var idempotent = new ScopedAsyncIdempotent<int, IntHolder>(holder);
49+
50+
idempotent.Dispose();
51+
52+
holder.disposed.Should().BeTrue();
53+
}
54+
55+
[Fact]
56+
public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
57+
{
58+
var enter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
59+
var resume = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
60+
61+
var idempotent = new ScopedAsyncIdempotent<int, IntHolder>();
62+
var winningNumber = 0;
63+
var winnerCount = 0;
64+
65+
Task<(bool r, Lifetime<IntHolder> l)> first = idempotent.TryCreateLifetimeAsync(1, async k =>
66+
{
67+
enter.SetResult(true);
68+
await resume.Task;
69+
70+
winningNumber = 1;
71+
Interlocked.Increment(ref winnerCount);
72+
return new IntHolder() { actualNumber = 1 };
73+
});
74+
75+
Task<(bool r, Lifetime<IntHolder> l)> second = idempotent.TryCreateLifetimeAsync(1, async k =>
76+
{
77+
enter.SetResult(true);
78+
await resume.Task;
79+
80+
winningNumber = 2;
81+
Interlocked.Increment(ref winnerCount);
82+
return new IntHolder() { actualNumber = 2 };
83+
});
84+
85+
await enter.Task;
86+
resume.SetResult(true);
87+
88+
var result1 = await first;
89+
var result2 = await second;
90+
91+
result1.r.Should().BeTrue();
92+
result2.r.Should().BeTrue();
93+
94+
result1.l.Value.actualNumber.Should().Be(winningNumber);
95+
result2.l.Value.actualNumber.Should().Be(winningNumber);
96+
97+
winnerCount.Should().Be(1);
98+
}
99+
100+
[Fact]
101+
public async Task WhenDisposedWhileInitResultIsDisposed()
102+
{
103+
var enter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
104+
var resume = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
105+
106+
var idempotent = new ScopedAsyncIdempotent<int, IntHolder>();
107+
var holder = new IntHolder() { actualNumber = 1 };
108+
109+
Task<(bool r, Lifetime<IntHolder> l)> first = idempotent.TryCreateLifetimeAsync(1, async k =>
110+
{
111+
enter.SetResult(true);
112+
await resume.Task;
113+
114+
return holder;
115+
});
116+
117+
await enter.Task;
118+
idempotent.Dispose();
119+
resume.SetResult(true);
120+
121+
var result = await first;
122+
123+
result.r.Should().BeFalse();
124+
result.l.Should().BeNull();
125+
126+
holder.disposed.Should().BeTrue();
127+
}
128+
129+
[Fact]
130+
public async Task WhenDisposedWhileThrowingNextInitIsDisposed()
131+
{
132+
var enter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
133+
var resume = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
134+
135+
var idempotent = new ScopedAsyncIdempotent<int, IntHolder>();
136+
var holder = new IntHolder() { actualNumber = 1 };
137+
138+
Task<(bool r, Lifetime<IntHolder> l)> first = idempotent.TryCreateLifetimeAsync(1, async k =>
139+
{
140+
enter.SetResult(true);
141+
await resume.Task;
142+
143+
throw new InvalidOperationException();
144+
});
145+
146+
await enter.Task;
147+
idempotent.Dispose();
148+
resume.SetResult(true);
149+
150+
// At this point, the scoped value is not created but the initializer is marked
151+
// to dispose the item. If no further calls are made, there is nothing to dispose.
152+
// If we create an item, to be in a consistent state we should dispose it.
153+
154+
Func<Task> tryCreateAsync = async () => { await first; };
155+
await tryCreateAsync.Should().ThrowAsync<InvalidOperationException>();
156+
157+
(bool r, Lifetime<IntHolder> l) result = await idempotent.TryCreateLifetimeAsync(1, k =>
158+
{
159+
return Task.FromResult(holder);
160+
});
161+
162+
result.r.Should().BeFalse();
163+
result.l.Should().BeNull();
164+
165+
holder.disposed.Should().BeTrue();
166+
}
167+
168+
private class IntHolder : IDisposable
169+
{
170+
public bool disposed;
171+
public int actualNumber;
172+
173+
public void Dispose()
174+
{
175+
disposed = true;
176+
}
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)