mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-18 10:26:11 +08:00
Fix freezes in _AsyncLock_ (#1549)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user