1565 high cpu load from asyncsignal (#1566)

This commit is contained in:
Christian
2022-11-09 19:56:29 +01:00
committed by GitHub
parent 5c74728387
commit 5ee0bb4b65
6 changed files with 67 additions and 20 deletions
@@ -52,12 +52,26 @@ namespace MQTTnet.Tests.Internal
} }
[TestMethod] [TestMethod]
public async Task Loop_Signal() public async Task Reset_Signal()
{ {
var asyncSignal = new AsyncSignal(); var asyncSignal = new AsyncSignal();
// WaitAsync should fail because no signal is available.
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
try
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)))
{
await asyncSignal.WaitAsync(timeout.Token);
}
Assert.Fail("This must fail because the signal is not yet set.");
}
catch (OperationCanceledException)
{
}
asyncSignal.Set(); asyncSignal.Set();
// WaitAsync should return directly because the signal is available. // WaitAsync should return directly because the signal is available.
+1 -1
View File
@@ -55,7 +55,7 @@ namespace MQTTnet.Tests.Server
}); });
} }
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(90)); SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(120));
Assert.AreEqual(100000, receivedMessages); Assert.AreEqual(100000, receivedMessages);
} }
+2 -2
View File
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
// Try to connect 50 clients at the same time. // Try to connect 50 clients at the same time.
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive))); var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
await Task.Delay(5000); await Task.Delay(TimeSpan.FromSeconds(10));
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList(); var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
@@ -162,7 +162,7 @@ namespace MQTTnet.Tests.Server
var sendClient = await testEnvironment.ConnectClient(option2); var sendClient = await testEnvironment.ConnectClient(option2);
await sendClient.PublishStringAsync("aaa", "1"); await sendClient.PublishStringAsync("aaa", "1");
await Task.Delay(3000); await Task.Delay(TimeSpan.FromSeconds(5));
Assert.AreEqual(true, hasReceive); Assert.AreEqual(true, hasReceive);
} }
+18 -12
View File
@@ -29,7 +29,7 @@ namespace MQTTnet.Internal
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
_signal?.Dispose(); _signal.Dispose();
_isDisposed = true; _isDisposed = true;
} }
@@ -48,10 +48,10 @@ namespace MQTTnet.Internal
{ {
if (_queue.TryDequeue(out var item)) if (_queue.TryDequeue(out var item))
{ {
return new AsyncQueueDequeueResult<TItem>(true, item); return AsyncQueueDequeueResult<TItem>.Success(item);
} }
return new AsyncQueueDequeueResult<TItem>(false, default); return AsyncQueueDequeueResult<TItem>.NonSuccess;
} }
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken) public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
@@ -60,36 +60,42 @@ namespace MQTTnet.Internal
{ {
try try
{ {
Task task; Task task = null;
lock (_syncRoot) lock (_syncRoot)
{ {
if (_isDisposed) if (_isDisposed)
{ {
return new AsyncQueueDequeueResult<TItem>(false, default); return AsyncQueueDequeueResult<TItem>.NonSuccess;
} }
task = _signal.WaitAsync(cancellationToken); if (_queue.IsEmpty)
{
task = _signal.WaitAsync(cancellationToken);
}
} }
await task.ConfigureAwait(false); if (task != null)
{
await task.ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return new AsyncQueueDequeueResult<TItem>(false, default); return AsyncQueueDequeueResult<TItem>.NonSuccess;
} }
if (_queue.TryDequeue(out var item)) if (_queue.TryDequeue(out var item))
{ {
return new AsyncQueueDequeueResult<TItem>(true, item); return AsyncQueueDequeueResult<TItem>.Success(item);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return new AsyncQueueDequeueResult<TItem>(false, default); return AsyncQueueDequeueResult<TItem>.NonSuccess;
} }
} }
return new AsyncQueueDequeueResult<TItem>(false, default); return AsyncQueueDequeueResult<TItem>.NonSuccess;
} }
} }
} }
@@ -4,8 +4,10 @@
namespace MQTTnet.Internal namespace MQTTnet.Internal
{ {
public class AsyncQueueDequeueResult<TItem> public sealed class AsyncQueueDequeueResult<TItem>
{ {
public static readonly AsyncQueueDequeueResult<TItem> NonSuccess = new AsyncQueueDequeueResult<TItem>(false, default);
public AsyncQueueDequeueResult(bool isSuccess, TItem item) public AsyncQueueDequeueResult(bool isSuccess, TItem item)
{ {
IsSuccess = isSuccess; IsSuccess = isSuccess;
@@ -15,5 +17,10 @@ namespace MQTTnet.Internal
public bool IsSuccess { get; } public bool IsSuccess { get; }
public TItem Item { get; } public TItem Item { get; }
public static AsyncQueueDequeueResult<TItem> Success(TItem item)
{
return new AsyncQueueDequeueResult<TItem>(true, item);
}
} }
} }
+23 -3
View File
@@ -33,9 +33,17 @@ namespace MQTTnet.Internal
{ {
_isSignaled = true; _isSignaled = true;
Cleanup();
// If there is already a waiting task let it run. // If there is already a waiting task let it run.
_waiter?.Approve(); if (_waiter != null)
_waiter = null; {
_waiter.Approve();
_waiter = null;
// Since we already got a waiter the signal must be reset right now!
_isSignaled = false;
}
} }
} }
@@ -47,8 +55,11 @@ namespace MQTTnet.Internal
{ {
ThrowIfDisposed(); ThrowIfDisposed();
Cleanup();
if (_isSignaled) if (_isSignaled)
{ {
_isSignaled = false;
return CompletedTask.Instance; return CompletedTask.Instance;
} }
@@ -58,7 +69,7 @@ namespace MQTTnet.Internal
{ {
throw new InvalidOperationException("Only one waiting task is permitted per async signal."); throw new InvalidOperationException("Only one waiting task is permitted per async signal.");
} }
_waiter.Dispose(); _waiter.Dispose();
} }
@@ -67,6 +78,15 @@ namespace MQTTnet.Internal
} }
} }
void Cleanup()
{
// Cleanup if the previous waiter was cancelled.
if (_waiter != null && _waiter.Task.IsCanceled)
{
_waiter = null;
}
}
void ThrowIfDisposed() void ThrowIfDisposed()
{ {
if (_isDisposed) if (_isDisposed)