Skip to content

Commit c3a3176

Browse files
maca88fredericDelaporte
authored andcommitted
Improve async locking
1 parent d68d402 commit c3a3176

22 files changed

+809
-207
lines changed

src/AsyncGenerator.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,6 @@
160160
transformation:
161161
configureAwaitArgument: false
162162
localFunctions: true
163-
asyncLock:
164-
type: NHibernate.Util.AsyncLock
165-
methodName: LockAsync
166163
documentationComments:
167164
addOrReplaceMethodSummary:
168165
- name: Commit
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Threading;
13+
using System.Threading.Tasks;
14+
using NHibernate.Util;
15+
using NUnit.Framework;
16+
17+
namespace NHibernate.Test.UtilityTest
18+
{
19+
public partial class AsyncReaderWriterLockFixture
20+
{
21+
22+
[Test]
23+
public async Task TestInvaildExitReadLockUsageAsync()
24+
{
25+
var l = new AsyncReaderWriterLock();
26+
var readReleaser = await (l.ReadLockAsync());
27+
var readReleaser2 = await (l.ReadLockAsync());
28+
29+
readReleaser.Dispose();
30+
readReleaser2.Dispose();
31+
Assert.Throws<InvalidOperationException>(() => readReleaser.Dispose());
32+
Assert.Throws<InvalidOperationException>(() => readReleaser2.Dispose());
33+
}
34+
35+
[Test]
36+
public async Task TestInvaildExitWriteLockUsageAsync()
37+
{
38+
var l = new AsyncReaderWriterLock();
39+
var writeReleaser = await (l.WriteLockAsync());
40+
41+
writeReleaser.Dispose();
42+
Assert.Throws<InvalidOperationException>(() => writeReleaser.Dispose());
43+
}
44+
}
45+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
<Compile Remove="**\NHSpecificTest\NH2188\**" />
4545
<Compile Remove="**\NHSpecificTest\NH3121\**" />
4646
</ItemGroup>
47+
<ItemGroup>
48+
<Compile Include="..\NHibernate\Util\AsyncReaderWriterLock.cs">
49+
<Link>UtilityTest\AsyncReaderWriterLock.cs</Link>
50+
</Compile>
51+
</ItemGroup>
4752
<ItemGroup>
4853
<PackageReference Include="log4net" Version="2.0.8" />
4954
<PackageReference Include="Microsoft.Data.SqlClient" Version="1.0.19269.1" />
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using NHibernate.Util;
5+
using NUnit.Framework;
6+
7+
namespace NHibernate.Test.UtilityTest
8+
{
9+
public partial class AsyncReaderWriterLockFixture
10+
{
11+
private readonly int _delay = 10;
12+
13+
[Test]
14+
public void TestBlocking()
15+
{
16+
var l = new AsyncReaderWriterLock();
17+
for (var i = 0; i < 10; i++)
18+
{
19+
var readReleaser = l.ReadLock();
20+
var readReleaserTask = Task.Run(() => l.ReadLock());
21+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
22+
23+
var writeReleaserTask = Task.Run(() => l.WriteLock());
24+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
25+
26+
readReleaser.Dispose();
27+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
28+
29+
readReleaserTask.Result.Dispose();
30+
Assert.That(writeReleaserTask.Wait(_delay), Is.True);
31+
32+
readReleaserTask = Task.Run(() => l.ReadLock());
33+
Assert.That(readReleaserTask.Wait(_delay), Is.False);
34+
35+
var writeReleaserTask2 = Task.Run(() => l.WriteLock());
36+
Assert.That(writeReleaserTask2.Wait(_delay), Is.False);
37+
38+
writeReleaserTask.Result.Dispose();
39+
Assert.That(readReleaserTask.Wait(_delay), Is.False);
40+
Assert.That(writeReleaserTask2.Wait(_delay), Is.True);
41+
42+
writeReleaserTask2.Result.Dispose();
43+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
44+
readReleaserTask.Result.Dispose();
45+
}
46+
}
47+
48+
[Test]
49+
public void TestBlockingAsync()
50+
{
51+
var l = new AsyncReaderWriterLock();
52+
for (var i = 0; i < 10; i++)
53+
{
54+
var readReleaserTask = l.ReadLockAsync();
55+
var readReleaserTask2 = l.ReadLockAsync();
56+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
57+
Assert.That(readReleaserTask2.Wait(_delay), Is.True);
58+
59+
var writeReleaserTask = l.WriteLockAsync();
60+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
61+
62+
readReleaserTask.Result.Dispose();
63+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
64+
65+
readReleaserTask2.Result.Dispose();
66+
Assert.That(writeReleaserTask.Wait(_delay), Is.True);
67+
68+
readReleaserTask = l.ReadLockAsync();
69+
Assert.That(readReleaserTask.Wait(_delay), Is.False);
70+
71+
var writeReleaserTask2 = l.WriteLockAsync();
72+
Assert.That(writeReleaserTask2.Wait(_delay), Is.False);
73+
74+
writeReleaserTask.Result.Dispose();
75+
Assert.That(readReleaserTask.Wait(_delay), Is.False);
76+
Assert.That(writeReleaserTask2.Wait(_delay), Is.True);
77+
78+
writeReleaserTask2.Result.Dispose();
79+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
80+
readReleaserTask.Result.Dispose();
81+
}
82+
}
83+
84+
[Test]
85+
public void TestInvaildExitReadLockUsage()
86+
{
87+
var l = new AsyncReaderWriterLock();
88+
var readReleaser = l.ReadLock();
89+
var readReleaser2 = l.ReadLock();
90+
91+
readReleaser.Dispose();
92+
readReleaser2.Dispose();
93+
Assert.Throws<InvalidOperationException>(() => readReleaser.Dispose());
94+
Assert.Throws<InvalidOperationException>(() => readReleaser2.Dispose());
95+
}
96+
97+
[Test]
98+
public void TestInvaildExitWriteLockUsage()
99+
{
100+
var l = new AsyncReaderWriterLock();
101+
var writeReleaser = l.WriteLock();
102+
103+
writeReleaser.Dispose();
104+
Assert.Throws<InvalidOperationException>(() => writeReleaser.Dispose());
105+
}
106+
107+
[Test]
108+
public void TestMixingSyncAndAsync()
109+
{
110+
var l = new AsyncReaderWriterLock();
111+
var readReleaser = l.ReadLock();
112+
var readReleaserTask = l.ReadLockAsync();
113+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
114+
115+
readReleaser.Dispose();
116+
readReleaserTask.Result.Dispose();
117+
118+
var writeReleaser = l.WriteLock();
119+
var writeReleaserTask = l.WriteLockAsync();
120+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
121+
122+
readReleaserTask = Task.Run(() => l.ReadLock());
123+
var readReleaserTask2 = l.ReadLockAsync();
124+
Assert.That(readReleaserTask.Wait(_delay), Is.False);
125+
Assert.That(readReleaserTask2.Wait(_delay), Is.False);
126+
127+
writeReleaser.Dispose();
128+
Assert.That(writeReleaserTask.Wait(_delay), Is.True);
129+
Assert.That(readReleaserTask.Wait(_delay), Is.False);
130+
Assert.That(readReleaserTask2.Wait(_delay), Is.False);
131+
132+
writeReleaserTask.Result.Dispose();
133+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
134+
Assert.That(readReleaserTask2.Wait(_delay), Is.True);
135+
}
136+
137+
[Test]
138+
public void TestWritePriorityOverReadAsync()
139+
{
140+
var l = new AsyncReaderWriterLock();
141+
for (var i = 0; i < 10; i++)
142+
{
143+
var writeReleaser = l.WriteLock();
144+
var readReleaserTask = l.ReadLockAsync();
145+
var writeReleaserTask = l.WriteLockAsync();
146+
147+
writeReleaser.Dispose();
148+
Assert.That(writeReleaserTask.Wait(_delay), Is.True);
149+
150+
writeReleaserTask.Result.Dispose();
151+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
152+
readReleaserTask.Result.Dispose();
153+
}
154+
}
155+
156+
[Test]
157+
public void TestPartialReleasingReadLockAsync()
158+
{
159+
var l = new AsyncReaderWriterLock();
160+
var readReleaserTask = l.ReadLockAsync();
161+
var readReleaserTask2 = l.ReadLockAsync();
162+
Assert.That(readReleaserTask.Wait(_delay), Is.True);
163+
Assert.That(readReleaserTask2.Wait(_delay), Is.True);
164+
165+
var writeReleaserTask = l.WriteLockAsync();
166+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
167+
168+
var readReleaserTask3 = l.ReadLockAsync();
169+
Assert.That(readReleaserTask3.Wait(_delay), Is.False);
170+
171+
readReleaserTask.Result.Dispose();
172+
Assert.That(writeReleaserTask.Wait(_delay), Is.False);
173+
Assert.That(readReleaserTask3.Wait(_delay), Is.False);
174+
175+
readReleaserTask2.Result.Dispose();
176+
Assert.That(writeReleaserTask.Wait(_delay), Is.True);
177+
Assert.That(readReleaserTask3.Wait(_delay), Is.False);
178+
179+
writeReleaserTask.Result.Dispose();
180+
Assert.That(readReleaserTask3.Wait(_delay), Is.True);
181+
}
182+
183+
[Test, Explicit]
184+
public async Task TestLoadSyncAndAsync()
185+
{
186+
var l = new AsyncReaderWriterLock();
187+
int readLockCount = 0, writeLockCount = 0;
188+
var incorrectLockCount = false;
189+
var threads = new Thread[10];
190+
var tasks = new Task[10];
191+
var masterRandom = new Random();
192+
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));
193+
194+
for (var i = 0; i < threads.Length; i++)
195+
{
196+
// Ensure that each random has its own unique seed
197+
var random = new Random(masterRandom.Next());
198+
threads[i] = new Thread(() => SyncLock(random, cancellationTokenSource.Token));
199+
threads[i].Start();
200+
}
201+
202+
for (var i = 0; i < tasks.Length; i++)
203+
{
204+
// Ensure that each random has its own unique seed
205+
var random = new Random(masterRandom.Next());
206+
tasks[i] = Task.Run(() => AsyncLock(random, cancellationTokenSource.Token));
207+
}
208+
209+
foreach (var thread in threads)
210+
{
211+
thread.Join();
212+
}
213+
214+
await Task.WhenAll(tasks).ConfigureAwait(false);
215+
Assert.That(incorrectLockCount, Is.False);
216+
217+
218+
void CheckLockCount()
219+
{
220+
var countIsCorrect = readLockCount == 0 && writeLockCount == 0 ||
221+
readLockCount > 0 && writeLockCount == 0 ||
222+
readLockCount == 0 && writeLockCount == 1;
223+
224+
if (!countIsCorrect)
225+
{
226+
Volatile.Write(ref incorrectLockCount, true);
227+
}
228+
}
229+
230+
void SyncLock(Random random, CancellationToken cancellationToken)
231+
{
232+
while (!cancellationToken.IsCancellationRequested)
233+
{
234+
var isRead = random.Next(100) < 80;
235+
var releaser = isRead ? l.ReadLock() : l.WriteLock();
236+
lock (l)
237+
{
238+
if (isRead)
239+
{
240+
readLockCount++;
241+
}
242+
else
243+
{
244+
writeLockCount++;
245+
}
246+
}
247+
248+
Thread.Sleep(10);
249+
250+
lock (l)
251+
{
252+
releaser.Dispose();
253+
if (isRead)
254+
{
255+
readLockCount--;
256+
}
257+
else
258+
{
259+
writeLockCount--;
260+
}
261+
}
262+
}
263+
}
264+
265+
async Task AsyncLock(Random random, CancellationToken cancellationToken)
266+
{
267+
while (!cancellationToken.IsCancellationRequested)
268+
{
269+
var isRead = random.Next(100) < 80;
270+
var releaser = isRead
271+
? await l.ReadLockAsync().ConfigureAwait(false)
272+
: await l.WriteLockAsync().ConfigureAwait(false);
273+
lock (l)
274+
{
275+
if (isRead)
276+
{
277+
readLockCount++;
278+
}
279+
else
280+
{
281+
writeLockCount++;
282+
}
283+
284+
CheckLockCount();
285+
}
286+
287+
await Task.Delay(10).ConfigureAwait(false);
288+
289+
lock (l)
290+
{
291+
releaser.Dispose();
292+
if (isRead)
293+
{
294+
readLockCount--;
295+
}
296+
else
297+
{
298+
writeLockCount--;
299+
}
300+
301+
CheckLockCount();
302+
}
303+
}
304+
}
305+
}
306+
}
307+
}

0 commit comments

Comments
 (0)