diff --git a/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs b/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs index 88ec56fa..9adac5dc 100644 --- a/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs @@ -52,12 +52,26 @@ namespace MQTTnet.Tests.Internal } [TestMethod] - public async Task Loop_Signal() + public async Task Reset_Signal() { var asyncSignal = new AsyncSignal(); + // WaitAsync should fail because no signal is available. 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(); // WaitAsync should return directly because the signal is available. diff --git a/Source/MQTTnet.Tests/Server/Load_Tests.cs b/Source/MQTTnet.Tests/Server/Load_Tests.cs index 7c446ee0..112d1d80 100644 --- a/Source/MQTTnet.Tests/Server/Load_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Load_Tests.cs @@ -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); } diff --git a/Source/MQTTnet.Tests/Server/Session_Tests.cs b/Source/MQTTnet.Tests/Server/Session_Tests.cs index d86d2e8d..6b9af994 100644 --- a/Source/MQTTnet.Tests/Server/Session_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Session_Tests.cs @@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server // Try to connect 50 clients at the same time. 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(); @@ -162,7 +162,7 @@ namespace MQTTnet.Tests.Server var sendClient = await testEnvironment.ConnectClient(option2); await sendClient.PublishStringAsync("aaa", "1"); - await Task.Delay(3000); + await Task.Delay(TimeSpan.FromSeconds(5)); Assert.AreEqual(true, hasReceive); } diff --git a/Source/MQTTnet/Internal/AsyncQueue.cs b/Source/MQTTnet/Internal/AsyncQueue.cs index c54f5467..52381a32 100644 --- a/Source/MQTTnet/Internal/AsyncQueue.cs +++ b/Source/MQTTnet/Internal/AsyncQueue.cs @@ -29,7 +29,7 @@ namespace MQTTnet.Internal { lock (_syncRoot) { - _signal?.Dispose(); + _signal.Dispose(); _isDisposed = true; } @@ -48,10 +48,10 @@ namespace MQTTnet.Internal { if (_queue.TryDequeue(out var item)) { - return new AsyncQueueDequeueResult(true, item); + return AsyncQueueDequeueResult.Success(item); } - return new AsyncQueueDequeueResult(false, default); + return AsyncQueueDequeueResult.NonSuccess; } public async Task> TryDequeueAsync(CancellationToken cancellationToken) @@ -60,36 +60,42 @@ namespace MQTTnet.Internal { try { - Task task; + Task task = null; lock (_syncRoot) { if (_isDisposed) { - return new AsyncQueueDequeueResult(false, default); + return AsyncQueueDequeueResult.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) { - return new AsyncQueueDequeueResult(false, default); + return AsyncQueueDequeueResult.NonSuccess; } if (_queue.TryDequeue(out var item)) { - return new AsyncQueueDequeueResult(true, item); + return AsyncQueueDequeueResult.Success(item); } } catch (OperationCanceledException) { - return new AsyncQueueDequeueResult(false, default); + return AsyncQueueDequeueResult.NonSuccess; } } - return new AsyncQueueDequeueResult(false, default); + return AsyncQueueDequeueResult.NonSuccess; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs b/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs index 6848e1fd..09543238 100644 --- a/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs +++ b/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs @@ -4,8 +4,10 @@ namespace MQTTnet.Internal { - public class AsyncQueueDequeueResult + public sealed class AsyncQueueDequeueResult { + public static readonly AsyncQueueDequeueResult NonSuccess = new AsyncQueueDequeueResult(false, default); + public AsyncQueueDequeueResult(bool isSuccess, TItem item) { IsSuccess = isSuccess; @@ -15,5 +17,10 @@ namespace MQTTnet.Internal public bool IsSuccess { get; } public TItem Item { get; } + + public static AsyncQueueDequeueResult Success(TItem item) + { + return new AsyncQueueDequeueResult(true, item); + } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncSignal.cs b/Source/MQTTnet/Internal/AsyncSignal.cs index cde8532c..217a5cce 100644 --- a/Source/MQTTnet/Internal/AsyncSignal.cs +++ b/Source/MQTTnet/Internal/AsyncSignal.cs @@ -33,9 +33,17 @@ namespace MQTTnet.Internal { _isSignaled = true; + Cleanup(); + // If there is already a waiting task let it run. - _waiter?.Approve(); - _waiter = null; + if (_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(); + Cleanup(); + if (_isSignaled) { + _isSignaled = false; return CompletedTask.Instance; } @@ -58,7 +69,7 @@ namespace MQTTnet.Internal { throw new InvalidOperationException("Only one waiting task is permitted per async signal."); } - + _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() { if (_isDisposed)