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
+2 -2
View File
@@ -195,7 +195,7 @@ namespace MQTTnet.Adapter
// This lock makes sure that multiple threads can send packets at the same time.
// This is required when a disconnect is sent from another thread while the
// worker thread is still sending publish packets etc.
using (await _syncRoot.WaitAsync(cancellationToken).ConfigureAwait(false))
using (await _syncRoot.EnterAsync(cancellationToken).ConfigureAwait(false))
{
// Check for cancellation here again because "WaitAsync" might take some time.
cancellationToken.ThrowIfCancellationRequested();
@@ -363,7 +363,7 @@ namespace MQTTnet.Adapter
var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0)
{
return new ReceivedMqttPacket(fixedHeader.Flags, PlatformAbstractionLayer.EmptyByteArraySegment, 2);
return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2);
}
var bodyLength = fixedHeader.RemainingLength;
+1
View File
@@ -8,6 +8,7 @@ namespace MQTTnet.Client
public interface IMqttClient : IDisposable
{
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
+9 -10
View File
@@ -9,7 +9,6 @@ using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
@@ -224,17 +223,17 @@ namespace MQTTnet.Client
public async Task PingAsync(CancellationToken cancellationToken = default)
{
if (!cancellationToken.CanBeCanceled)
if (cancellationToken.CanBeCanceled)
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
}
}
else
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
}
}
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default)
@@ -369,7 +368,7 @@ namespace MQTTnet.Client
throw new MqttProtocolViolationException("Received a not supported QoS level.");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
@@ -490,7 +489,7 @@ namespace MQTTnet.Client
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
bool DisconnectIsPendingOrFinished()
@@ -550,7 +549,7 @@ namespace MQTTnet.Client
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
@@ -604,7 +603,7 @@ namespace MQTTnet.Client
return SendAsync(pubRelPacket, cancellationToken);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
+4 -4
View File
@@ -6,13 +6,13 @@ using System;
using System.Runtime.CompilerServices;
using System.Text;
using MQTTnet.Exceptions;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Formatter
{
public sealed class MqttBufferReader
{
byte[] _buffer = PlatformAbstractionLayer.EmptyByteArray;
byte[] _buffer = EmptyBuffer.Array;
int _initialPosition;
int _length;
int _position;
@@ -29,7 +29,7 @@ namespace MQTTnet.Formatter
if (length == 0)
{
return PlatformAbstractionLayer.EmptyByteArray;
return EmptyBuffer.Array;
}
ValidateReceiveBuffer(length);
@@ -66,7 +66,7 @@ namespace MQTTnet.Formatter
if (bufferLength == 0)
{
return PlatformAbstractionLayer.EmptyByteArray;
return EmptyBuffer.Array;
}
var buffer = new byte[bufferLength];
+2 -1
View File
@@ -5,12 +5,13 @@
using System;
using System.Linq;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Formatter
{
public readonly struct MqttPacketBuffer
{
static readonly ArraySegment<byte> EmptyPayload = PlatformAbstractionLayer.EmptyByteArraySegment;
static readonly ArraySegment<byte> EmptyPayload = EmptyBuffer.ArraySegment;
public MqttPacketBuffer(ArraySegment<byte> packet, ArraySegment<byte> payload)
{
@@ -15,14 +15,20 @@ namespace MQTTnet.Implementations
public sealed class CrossPlatformSocket : IDisposable
{
readonly Socket _socket;
#if !NET5_0_OR_GREATER
readonly Action _socketDisposeAction;
#endif
NetworkStream _networkStream;
public CrossPlatformSocket(AddressFamily addressFamily)
{
_socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose;
#endif
}
public CrossPlatformSocket()
@@ -31,17 +37,76 @@ namespace MQTTnet.Implementations
// will make use of dual mode in the .net framework.
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose;
#endif
}
public CrossPlatformSocket(Socket socket)
CrossPlatformSocket(Socket socket)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_networkStream = new NetworkStream(socket, true);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose;
#endif
}
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public bool IsConnected => _socket.Connected;
public bool KeepAlive
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive) as int? == 1;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, value ? 1 : 0);
}
public int TcpKeepAliveInterval
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
#else
get { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
#endif
}
public int TcpKeepAliveRetryCount
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
#else
get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
#endif
}
public int TcpKeepAliveTime
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
#else
get { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
#endif
}
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public bool NoDelay
{
// We cannot use the _NoDelay_ property from the socket because there is an issue in .NET 4.5.2, 4.6.
@@ -51,24 +116,20 @@ namespace MQTTnet.Implementations
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0);
}
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public int ReceiveBufferSize
{
get => _socket.ReceiveBufferSize;
set => _socket.ReceiveBufferSize = value;
}
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) as int? != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public int SendBufferSize
{
get => _socket.SendBufferSize;
@@ -81,16 +142,6 @@ namespace MQTTnet.Implementations
set => _socket.SendTimeout = value;
}
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public bool IsConnected => _socket.Connected;
public async Task<CrossPlatformSocket> AcceptAsync()
{
try
@@ -109,8 +160,6 @@ namespace MQTTnet.Implementations
}
}
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public void Bind(EndPoint localEndPoint)
{
if (localEndPoint is null)
@@ -121,11 +170,6 @@ namespace MQTTnet.Implementations
_socket.Bind(localEndPoint);
}
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
}
public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken)
{
if (host is null)
@@ -137,11 +181,17 @@ namespace MQTTnet.Implementations
try
{
_networkStream?.Dispose();
#if NET5_0_OR_GREATER
if (_networkStream != null)
{
await _networkStream.DisposeAsync().ConfigureAwait(false);
}
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else
_networkStream?.Dispose();
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_socketDisposeAction))
{
@@ -175,20 +225,26 @@ namespace MQTTnet.Implementations
}
}
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
public void Dispose()
{
try
_networkStream?.Dispose();
_socket?.Dispose();
}
public NetworkStream GetStream()
{
var networkStream = _networkStream;
if (networkStream == null)
{
#if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
throw new IOException("The socket is not connected.");
}
return networkStream;
}
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
}
public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -208,28 +264,27 @@ namespace MQTTnet.Implementations
}
}
public NetworkStream GetStream()
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{
var networkStream = _networkStream;
if (networkStream == null)
try
{
throw new IOException("The socket is not connected.");
#if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
}
return networkStream;
}
public void Dispose()
{
_networkStream?.Dispose();
_socket?.Dispose();
}
#if NET452 || NET461
sealed class SocketWrapper
{
readonly Socket _socket;
readonly ArraySegment<byte> _buffer;
readonly Socket _socket;
readonly SocketFlags _socketFlags;
public SocketWrapper(Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -239,16 +294,28 @@ namespace MQTTnet.Implementations
_socketFlags = socketFlags;
}
public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
}
public static IAsyncResult BeginReceive(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginReceive(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
return socketWrapper._socket.BeginReceive(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
}
public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
}
}
#endif
@@ -14,6 +14,7 @@ using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
{
@@ -154,7 +155,7 @@ namespace MQTTnet.Implementations
public Task DisconnectAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.FromResult(0);
return CompletedTask.Instance;
}
public void Dispose()
@@ -13,6 +13,7 @@ using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
{
@@ -56,13 +57,13 @@ namespace MQTTnet.Implementations
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public Task StopAsync()
{
Cleanup();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public void Dispose()
@@ -124,7 +125,7 @@ namespace MQTTnet.Implementations
var clientHandler = ClientHandler;
if (clientHandler == null)
{
return Task.FromResult(0);
return CompletedTask.Instance;
}
return clientHandler(channelAdapter);
@@ -117,7 +117,7 @@ namespace MQTTnet.Implementations
return;
}
using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false))
using (await _sendLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
}
@@ -1,43 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Implementations
{
public static class PlatformAbstractionLayer
{
#if NET452
public static Task CompletedTask { get; } = Task.FromResult(0);
public static byte[] EmptyByteArray { get; } = new byte[0];
#else
public static Task CompletedTask { get; } = Task.CompletedTask;
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
#endif
public static ArraySegment<byte> EmptyByteArraySegment { get; } = new ArraySegment<byte>(EmptyByteArray);
public static void Sleep(TimeSpan timeout)
{
#if !NETSTANDARD1_3 && !WINDOWS_UWP
try
{
System.Threading.Thread.Sleep(timeout);
}
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
}
}
}
+4 -5
View File
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
@@ -37,7 +36,7 @@ namespace MQTTnet.Internal
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
@@ -53,7 +52,7 @@ namespace MQTTnet.Internal
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
@@ -86,7 +85,7 @@ namespace MQTTnet.Internal
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
@@ -102,7 +101,7 @@ namespace MQTTnet.Internal
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
@@ -36,7 +36,7 @@ namespace MQTTnet.Internal
if (_handler != null)
{
_handler.Invoke(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
if (_asyncHandler != null)
+83 -105
View File
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -12,173 +12,151 @@ namespace MQTTnet.Internal
{
public sealed class AsyncLock : IDisposable
{
/*
* This async supporting lock does not support reentrancy!
*/
readonly List<Releaser> _queuedTasks = new List<Releaser>(64);
readonly Task<IDisposable> _releaserTaskWithDirectApproval;
readonly Releaser _releaserWithDirectApproval;
readonly Task<IDisposable> _completedTask;
readonly IDisposable _releaser;
readonly object _syncRoot = new object();
readonly Queue<AsyncLockWaiter> _waiters = new Queue<AsyncLockWaiter>(64);
volatile bool _isDisposed;
bool _isLocked;
bool _isDisposed;
public AsyncLock()
{
_releaserWithDirectApproval = new Releaser(this, null, CancellationToken.None);
_releaserTaskWithDirectApproval = Task.FromResult((IDisposable)_releaserWithDirectApproval);
_releaser = new Releaser(this);
_completedTask = Task.FromResult(_releaser);
}
public void Dispose()
{
lock (_syncRoot)
{
foreach (var waitingTask in _queuedTasks)
{
waitingTask.Fail(new ObjectDisposedException(nameof(AsyncLock)));
}
_queuedTasks.Clear();
_isDisposed = true;
while (_waiters.Any())
{
_waiters.Dequeue().Dispose();
}
}
}
public Task<IDisposable> WaitAsync(CancellationToken cancellationToken)
public Task<IDisposable> EnterAsync(CancellationToken cancellationToken = default)
{
var hasDirectApproval = false;
Releaser releaser;
cancellationToken.ThrowIfCancellationRequested();
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(AsyncLock));
}
lock (_syncRoot)
{
if (_isDisposed)
if (!_isLocked)
{
throw new ObjectDisposedException(nameof(AsyncLock));
}
if (_queuedTasks.Count == 0)
{
// There is no other waiting task apart from the current one.
// So we can approve the current task directly.
releaser = _releaserWithDirectApproval;
hasDirectApproval = true;
Debug.WriteLine("AsyncLock: Task -1 directly approved.");
}
else
{
releaser = new Releaser(this, new TaskCompletionSource<IDisposable>(), cancellationToken);
_isLocked = true;
return _completedTask;
}
_queuedTasks.Add(releaser);
var waiter = new AsyncLockWaiter(cancellationToken);
_waiters.Enqueue(waiter);
return waiter.Task;
}
if (!hasDirectApproval)
{
return releaser.Task;
}
return _releaserTaskWithDirectApproval;
}
void Release(Releaser releaser)
void Release()
{
lock (_syncRoot)
{
if (_isDisposed)
{
// There is no much left to do!
// All waiters have been canceled with a ObjectDisposedException.
// So there is nothing left to do.
return;
}
var activeTask = _queuedTasks[0];
if (!ReferenceEquals(activeTask, releaser))
{
throw new InvalidOperationException("The active task must be the current releaser.");
}
// Assume that there is no waiter left first.
_isLocked = false;
_queuedTasks.RemoveAt(0);
while (_queuedTasks.Count > 0)
// Try to find the next waiter which can be approved.
// Some of them might be canceled already so it is not
// guaranteed that the very next waiter is the correct one.
while (_waiters.Any())
{
var nextTask = _queuedTasks[0];
if (!nextTask.IsPending)
var waiter = _waiters.Dequeue();
var isApproved = waiter.Approve(_releaser);
waiter.Dispose();
if (isApproved)
{
// Dequeue all canceled or failed tasks.
_queuedTasks.RemoveAt(0);
continue;
_isLocked = true;
return;
}
nextTask.Approve();
return;
}
Debug.WriteLine("AsyncLock: No Task pending.");
}
}
sealed class Releaser : IDisposable
sealed class AsyncLockWaiter : IDisposable
{
readonly AsyncLock _asyncLock;
readonly CancellationToken _cancellationToken;
readonly int _id;
readonly TaskCompletionSource<IDisposable> _promise;
readonly CancellationTokenRegistration _cancellationRegistration;
readonly bool _hasCancellationRegistration;
readonly AsyncTaskCompletionSource<IDisposable> _promise = new AsyncTaskCompletionSource<IDisposable>();
// ReSharper disable once FieldCanBeMadeReadOnly.Local
CancellationTokenRegistration _cancellationTokenRegistration;
internal Releaser(AsyncLock asyncLock, TaskCompletionSource<IDisposable> promise, CancellationToken cancellationToken)
public AsyncLockWaiter(CancellationToken cancellationToken)
{
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock));
_promise = promise;
_cancellationToken = cancellationToken;
cancellationToken.ThrowIfCancellationRequested();
if (cancellationToken.CanBeCanceled)
{
_cancellationTokenRegistration = cancellationToken.Register(Cancel);
_cancellationRegistration = cancellationToken.Register(Cancel);
_hasCancellationRegistration = true;
}
_id = promise?.Task.Id ?? -1;
Debug.WriteLine($"AsyncLock: Task {_id} queued.");
}
public bool IsPending => _promise != null && !_promise.Task.IsCanceled && !_promise.Task.IsFaulted && !_promise.Task.IsCompleted;
public Task<IDisposable> Task => _promise.Task;
public Task<IDisposable> Task => _promise?.Task;
public void Approve()
public bool Approve(IDisposable scope)
{
_promise?.TrySetResult(this);
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
Debug.WriteLine($"AsyncLock: Task {_id} approved.");
if (_promise.Task.IsCompleted)
{
return false;
}
return _promise.TrySetResult(scope);
}
public void Dispose()
{
if (_cancellationToken.CanBeCanceled)
if (_hasCancellationRegistration)
{
_cancellationTokenRegistration.Dispose();
_cancellationRegistration.Dispose();
}
Debug.WriteLine($"AsyncLock: Task {_id} completed.");
_asyncLock.Release(this);
}
public void Fail(Exception exception)
{
_promise?.TrySetException(exception);
Debug.WriteLine($"AsyncLock: Task {_id} failed ({exception.GetType().Name}).");
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncLockWaiter)));
}
void Cancel()
{
_promise?.TrySetCanceled();
_promise.TrySetCanceled();
}
}
Debug.WriteLine($"AsyncLock: Task {_id} canceled.");
readonly struct Releaser : IDisposable
{
readonly AsyncLock _asyncLock;
public Releaser(AsyncLock asyncLock)
{
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock));
}
public void Dispose()
{
_asyncLock.Release();
}
}
}
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Internal
@@ -22,7 +23,7 @@ namespace MQTTnet.Internal
public Task<TResult> Task => _taskCompletionSource.Task;
public bool TrySetCanceled()
public void TrySetCanceled()
{
#if NET452
// To prevent deadlocks it is required to call the _TrySetCanceled_ method
@@ -31,19 +32,14 @@ namespace MQTTnet.Internal
// do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetCanceled());
return true;
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
#else
return _taskCompletionSource.TrySetCanceled();
_taskCompletionSource.TrySetCanceled();
#endif
}
public bool TrySetException(Exception exception)
public void TrySetException(Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
#if NET452
// To prevent deadlocks it is required to call the _TrySetException_ method
// from a new thread because the awaiting code will not(!) be executed in
@@ -51,9 +47,9 @@ namespace MQTTnet.Internal
// do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetException(exception));
return true;
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
#else
return _taskCompletionSource.TrySetException(exception);
_taskCompletionSource.TrySetException(exception);
#endif
}
@@ -65,7 +61,14 @@ namespace MQTTnet.Internal
// a new thread automatically (due to await). Furthermore _this_ thread will
// do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously
if (_taskCompletionSource.Task.IsCompleted)
{
return false;
}
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetResult(result));
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
return true;
#else
return _taskCompletionSource.TrySetResult(result);
+17
View File
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace MQTTnet.Internal
{
public static class CompletedTask
{
#if NET452
public static readonly Task Instance = Task.FromResult(true);
#else
public static readonly Task Instance = Task.CompletedTask;
#endif
}
}
+19
View File
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Internal
{
public static class EmptyBuffer
{
#if NET452
public static readonly byte[] Array = new byte[0];
#else
public static readonly byte[] Array = System.Array.Empty<byte>();
#endif
public static readonly ArraySegment<byte> ArraySegment = new ArraySegment<byte>(Array, 0, 0);
}
}
-1
View File
@@ -14,7 +14,6 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -10,6 +10,7 @@ using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -88,7 +89,7 @@ namespace MQTTnet.Server
/// </summary>
public uint MaximumPacketSize => _connectPacket.MaximumPacketSize;
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
public string Password => Encoding.UTF8.GetString(RawPassword ?? EmptyBuffer.Array);
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
+3 -3
View File
@@ -181,7 +181,7 @@ namespace MQTTnet.Server
return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
@@ -193,7 +193,7 @@ namespace MQTTnet.Server
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket)
@@ -205,7 +205,7 @@ namespace MQTTnet.Server
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
@@ -306,6 +306,9 @@ namespace MQTTnet.Server
await client.RunAsync().ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException)
{
}
@@ -479,7 +482,7 @@ namespace MQTTnet.Server
sessionShouldPersist = !connectPacket.CleanSession;
}
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await _createConnectionSyncRoot.EnterAsync().ConfigureAwait(false))
{
MqttSession session;
lock (_sessionsManagementLock)
@@ -537,6 +540,7 @@ namespace MQTTnet.Server
MqttClientDisconnectType.Takeover,
existingClient.Endpoint,
existingClient.Session.Items);
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
@@ -5,10 +5,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server
@@ -85,7 +83,7 @@ namespace MQTTnet.Server
}
else
{
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? PlatformAbstractionLayer.EmptyByteArray))
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? EmptyBuffer.Array))
{
_messages[applicationMessage.Topic] = applicationMessage;
saveIsRequired = true;
@@ -103,7 +101,7 @@ namespace MQTTnet.Server
if (saveIsRequired)
{
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await _storageAccessLock.EnterAsync().ConfigureAwait(false))
{
var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave);
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
@@ -132,7 +130,7 @@ namespace MQTTnet.Server
_messages.Clear();
}
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await _storageAccessLock.EnterAsync().ConfigureAwait(false))
{
await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
}
@@ -6,7 +6,6 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
@@ -14,16 +13,20 @@ namespace MQTTnet.Server
{
public sealed class MqttServerKeepAliveMonitor
{
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _options;
readonly MqttClientSessionsManager _sessionsManager;
readonly MqttNetSourceLogger _logger;
public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor));
}
@@ -45,7 +48,7 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested)
{
TryProcessClients();
PlatformAbstractionLayer.Sleep(_options.KeepAliveMonitorInterval);
Sleep(_options.KeepAliveMonitorInterval);
}
}
catch (OperationCanceledException)
@@ -61,13 +64,22 @@ namespace MQTTnet.Server
}
}
void TryProcessClients()
static void Sleep(TimeSpan timeout)
{
var now = DateTime.UtcNow;
foreach (var client in _sessionsManager.GetClients())
#if !NETSTANDARD1_3 && !WINDOWS_UWP
try
{
TryProcessClient(client, now);
Thread.Sleep(timeout);
}
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
}
void TryProcessClient(MqttClient connection, DateTime now)
@@ -119,5 +131,14 @@ namespace MQTTnet.Server
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id);
}
}
void TryProcessClients()
{
var now = DateTime.UtcNow;
foreach (var client in _sessionsManager.GetClients())
{
TryProcessClient(client, now);
}
}
}
}
}
+1 -2
View File
@@ -11,7 +11,6 @@ using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -172,7 +171,7 @@ namespace MQTTnet.Server
{
ThrowIfNotStarted();
return _retainedMessagesManager?.ClearMessages() ?? PlatformAbstractionLayer.CompletedTask;
return _retainedMessagesManager?.ClearMessages() ?? CompletedTask.Instance;
}
public Task DisconnectClientAsync(string id, MqttDisconnectReasonCode reasonCode)
@@ -6,7 +6,6 @@ using System;
using System.Collections;
using System.Threading.Tasks;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server
@@ -34,8 +33,8 @@ namespace MQTTnet.Server
var publishPacketFactory = new MqttPublishPacketFactory();
_session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketFactory.Create(applicationMessage)));
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage)