Skip to content

Commit d68bdf8

Browse files
authored
Improve async locking (nhibernate#2147)
1 parent a05f6a9 commit d68bdf8

22 files changed

+1185
-202
lines changed

src/AsyncGenerator.yml

-3
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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.Collections.Generic;
13+
using System.Linq;
14+
using System.Threading;
15+
using System.Threading.Tasks;
16+
using NHibernate.Util;
17+
using NUnit.Framework;
18+
19+
namespace NHibernate.Test.UtilityTest
20+
{
21+
public partial class AsyncReaderWriterLockFixture
22+
{
23+
24+
[Test, Explicit]
25+
public async Task TestConcurrentReadWriteAsync()
26+
{
27+
var l = new AsyncReaderWriterLock();
28+
for (var i = 0; i < 2; i++)
29+
{
30+
var writeReleaser = await (l.WriteLockAsync());
31+
Assert.That(l.Writing, Is.True);
32+
33+
var secondWriteSemaphore = new SemaphoreSlim(0);
34+
var secondWriteReleaser = default(AsyncReaderWriterLock.Releaser);
35+
var secondWriteThread = new Thread(
36+
() =>
37+
{
38+
secondWriteSemaphore.Wait();
39+
secondWriteReleaser = l.WriteLock();
40+
});
41+
secondWriteThread.Priority = ThreadPriority.Highest;
42+
secondWriteThread.Start();
43+
await (AssertEqualValueAsync(() => secondWriteThread.ThreadState == ThreadState.WaitSleepJoin, true));
44+
45+
var secondReadThreads = new Thread[20];
46+
var secondReadReleasers = new AsyncReaderWriterLock.Releaser[secondReadThreads.Length];
47+
var secondReadSemaphore = new SemaphoreSlim(0);
48+
for (var j = 0; j < secondReadReleasers.Length; j++)
49+
{
50+
var index = j;
51+
var thread = new Thread(
52+
() =>
53+
{
54+
secondReadSemaphore.Wait();
55+
secondReadReleasers[index] = l.ReadLock();
56+
});
57+
thread.Priority = ThreadPriority.Highest;
58+
secondReadThreads[j] = thread;
59+
thread.Start();
60+
}
61+
62+
await (AssertEqualValueAsync(() => secondReadThreads.All(o => o.ThreadState == ThreadState.WaitSleepJoin), true));
63+
64+
var firstReadReleaserTasks = new Task[30];
65+
var firstReadStopSemaphore = new SemaphoreSlim(0);
66+
for (var j = 0; j < firstReadReleaserTasks.Length; j++)
67+
{
68+
firstReadReleaserTasks[j] = Task.Run(async () =>
69+
{
70+
var releaser = await (l.ReadLockAsync());
71+
await (firstReadStopSemaphore.WaitAsync());
72+
releaser.Dispose();
73+
});
74+
}
75+
76+
await (AssertEqualValueAsync(() => l.ReadersWaiting, firstReadReleaserTasks.Length, waitDelay: 60000));
77+
78+
writeReleaser.Dispose();
79+
secondWriteSemaphore.Release();
80+
secondReadSemaphore.Release(secondReadThreads.Length);
81+
await (Task.Delay(1000));
82+
firstReadStopSemaphore.Release(firstReadReleaserTasks.Length);
83+
84+
await (AssertEqualValueAsync(() => firstReadReleaserTasks.All(o => o.IsCompleted), true));
85+
Assert.That(l.ReadersWaiting, Is.EqualTo(secondReadThreads.Length));
86+
Assert.That(l.CurrentReaders, Is.EqualTo(0));
87+
await (AssertEqualValueAsync(() => secondWriteThread.IsAlive, false));
88+
await (AssertEqualValueAsync(() => secondReadThreads.All(o => o.IsAlive), true));
89+
90+
secondWriteReleaser.Dispose();
91+
await (AssertEqualValueAsync(() => secondReadThreads.All(o => !o.IsAlive), true));
92+
93+
Assert.That(l.ReadersWaiting, Is.EqualTo(0));
94+
Assert.That(l.CurrentReaders, Is.EqualTo(secondReadThreads.Length));
95+
96+
foreach (var secondReadReleaser in secondReadReleasers)
97+
{
98+
secondReadReleaser.Dispose();
99+
}
100+
101+
Assert.That(l.ReadersWaiting, Is.EqualTo(0));
102+
Assert.That(l.CurrentReaders, Is.EqualTo(0));
103+
}
104+
}
105+
106+
[Test]
107+
public async Task TestInvaildExitReadLockUsageAsync()
108+
{
109+
var l = new AsyncReaderWriterLock();
110+
var readReleaser = await (l.ReadLockAsync());
111+
var readReleaser2 = await (l.ReadLockAsync());
112+
113+
readReleaser.Dispose();
114+
readReleaser2.Dispose();
115+
Assert.Throws<InvalidOperationException>(() => readReleaser.Dispose());
116+
Assert.Throws<InvalidOperationException>(() => readReleaser2.Dispose());
117+
}
118+
119+
[Test]
120+
public void TestOperationAfterDisposeAsync()
121+
{
122+
var l = new AsyncReaderWriterLock();
123+
l.Dispose();
124+
125+
Assert.ThrowsAsync<ObjectDisposedException>(() => l.ReadLockAsync());
126+
Assert.ThrowsAsync<ObjectDisposedException>(() => l.WriteLockAsync());
127+
}
128+
129+
[Test]
130+
public async Task TestInvaildExitWriteLockUsageAsync()
131+
{
132+
var l = new AsyncReaderWriterLock();
133+
var writeReleaser = await (l.WriteLockAsync());
134+
135+
writeReleaser.Dispose();
136+
Assert.Throws<InvalidOperationException>(() => writeReleaser.Dispose());
137+
}
138+
139+
private static async Task LockAsync(
140+
AsyncReaderWriterLock readWriteLock,
141+
Random random,
142+
LockStatistics lockStatistics,
143+
System.Action checkLockAction,
144+
Func<bool> canContinue, CancellationToken cancellationToken = default(CancellationToken))
145+
{
146+
while (canContinue())
147+
{
148+
var isRead = random.Next(100) < 80;
149+
var releaser = isRead ? await (readWriteLock.ReadLockAsync()) : await (readWriteLock.WriteLockAsync());
150+
lock (readWriteLock)
151+
{
152+
if (isRead)
153+
{
154+
lockStatistics.ReadLockCount++;
155+
}
156+
else
157+
{
158+
lockStatistics.WriteLockCount++;
159+
}
160+
161+
checkLockAction();
162+
}
163+
164+
await (Task.Delay(10, cancellationToken));
165+
166+
lock (readWriteLock)
167+
{
168+
releaser.Dispose();
169+
if (isRead)
170+
{
171+
lockStatistics.ReadLockCount--;
172+
}
173+
else
174+
{
175+
lockStatistics.WriteLockCount--;
176+
}
177+
178+
checkLockAction();
179+
}
180+
}
181+
}
182+
183+
private static async Task AssertEqualValueAsync<T>(Func<T> getValueFunc, T value, Task task = null, int waitDelay = 5000, CancellationToken cancellationToken = default(CancellationToken))
184+
{
185+
var currentTime = 0;
186+
var step = 5;
187+
while (currentTime < waitDelay)
188+
{
189+
if (task != null)
190+
{
191+
task.Wait(step);
192+
}
193+
else
194+
{
195+
await (Task.Delay(step, cancellationToken));
196+
}
197+
198+
currentTime += step;
199+
if (getValueFunc().Equals(value))
200+
{
201+
return;
202+
}
203+
204+
step *= 2;
205+
}
206+
207+
Assert.That(getValueFunc(), Is.EqualTo(value));
208+
}
209+
210+
private static Task AssertTaskCompletedAsync(Task task, CancellationToken cancellationToken = default(CancellationToken))
211+
{
212+
return AssertEqualValueAsync(() => task.IsCompleted, true, task, cancellationToken: cancellationToken);
213+
}
214+
}
215+
}

src/NHibernate.Test/NHibernate.Test.csproj

+5
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" />

0 commit comments

Comments
 (0)