Fix freezes in _AsyncLock_ (#1549)

This commit is contained in:
Christian
2022-10-23 08:16:45 +02:00
committed by GitHub
parent 36736914bc
commit d2ce9d607a
64 changed files with 798 additions and 516 deletions
+159 -19
View File
@@ -12,26 +12,130 @@ using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal
{
[TestClass]
public class AsyncLock_Tests
public sealed class AsyncLock_Tests
{
[TestMethod]
public void Lock_10_Parallel_Tasks()
public async Task Cancellation_Of_Awaiter()
{
const int ThreadsCount = 10;
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
var counter = 0;
Debug.WriteLine("Prepared locked lock.");
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 1 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 2 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 3 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 4 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 5 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(6)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 6 incremented");
counter++;
}
}
});
Debug.WriteLine("Delay before release...");
await Task.Delay(TimeSpan.FromSeconds(3.1));
releaser.Dispose();
Debug.WriteLine("Wait for all tasks...");
await Task.Delay(TimeSpan.FromSeconds(6.1));
Assert.AreEqual(3, counter);
}
[TestMethod]
public void Lock_Parallel_Tasks()
{
const int taskCount = 50;
var @lock = new AsyncLock();
var tasks = new Task[taskCount];
var globalI = 0;
for (var i = 0; i < ThreadsCount; i++)
for (var i = 0; i < taskCount; i++)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(
tasks[i] = Task.Run(
async () =>
{
using (await @lock.WaitAsync(CancellationToken.None))
using (await @lock.EnterAsync())
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
await Task.Delay(5); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
@@ -39,8 +143,8 @@ namespace MQTTnet.Tests.Internal
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Task.WaitAll(threads);
Assert.AreEqual(ThreadsCount, globalI);
Task.WaitAll(tasks);
Assert.AreEqual(taskCount, globalI);
}
[TestMethod]
@@ -57,7 +161,7 @@ namespace MQTTnet.Tests.Internal
threads[i] = Task.Run(
async () =>
{
using (await @lock.WaitAsync(CancellationToken.None))
using (await @lock.EnterAsync())
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
@@ -96,7 +200,7 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock();
for (var i = 0; i < 100; i++)
{
using (await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await @lock.EnterAsync().ConfigureAwait(false))
{
sum++;
}
@@ -112,11 +216,11 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock();
// This call will never "release" the lock due to missing _using_.
await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
await @lock.EnterAsync().ConfigureAwait(false);
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
await @lock.WaitAsync(cts.Token).ConfigureAwait(false);
await @lock.EnterAsync(cts.Token).ConfigureAwait(false);
}
}
@@ -125,15 +229,15 @@ namespace MQTTnet.Tests.Internal
{
var asyncLock = new AsyncLock();
var releaser = await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
var releaser = await asyncLock.EnterAsync().ConfigureAwait(false);
try
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
await asyncLock.WaitAsync(timeout.Token).ConfigureAwait(false);
await asyncLock.EnterAsync(timeout.Token).ConfigureAwait(false);
}
Assert.Fail("Exception should be thrown!");
}
catch (OperationCanceledException)
@@ -142,10 +246,46 @@ namespace MQTTnet.Tests.Internal
releaser.Dispose();
using (await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await asyncLock.EnterAsync(CancellationToken.None).ConfigureAwait(false))
{
// When the method finished, the thread got access.
}
}
[TestMethod]
public async Task Use_After_Cancellation()
{
var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
await @lock.EnterAsync(cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Expected exception!
}
releaser.Dispose();
// Regular usage after cancellation.
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
}
}
}