Fixed a race condition in client and server.

This commit is contained in:
Christian
2018-03-31 23:10:14 +02:00
parent cea984cc2f
commit 8f96acaec4
14 changed files with 224 additions and 127 deletions
+2
View File
@@ -15,6 +15,8 @@
* [Client] Added the "IsStarted" property for the managed client. * [Client] Added the "IsStarted" property for the managed client.
* [Client] Optimized stream buffer for UWP apps. * [Client] Optimized stream buffer for UWP apps.
* [Client] Added the _BufferSize_ to the TCP options. * [Client] Added the _BufferSize_ to the TCP options.
* [Client] Fixed a race condition which leads to exceptions when reconnecting rapidly.
* [Server] Fixed a race condition which leads to exceptions when clients are reconnecting rapidly.
* [Core] Fixed some issues in stream and socket handling. * [Core] Fixed some issues in stream and socket handling.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2018</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
@@ -7,7 +7,7 @@ using MQTTnet.Serializer;
namespace MQTTnet.Adapter namespace MQTTnet.Adapter
{ {
public interface IMqttChannelAdapter public interface IMqttChannelAdapter : IDisposable
{ {
IMqttPacketSerializer PacketSerializer { get; } IMqttPacketSerializer PacketSerializer { get; }
@@ -14,11 +14,12 @@ using MQTTnet.Serializer;
namespace MQTTnet.Adapter namespace MQTTnet.Adapter
{ {
public class MqttChannelAdapter : IMqttChannelAdapter public sealed class MqttChannelAdapter : IMqttChannelAdapter
{ {
private const uint ErrorOperationAborted = 0x800703E3; private const uint ErrorOperationAborted = 0x800703E3;
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
private bool _isDisposed;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private readonly IMqttChannel _channel; private readonly IMqttChannel _channel;
@@ -34,6 +35,7 @@ namespace MQTTnet.Adapter
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout)
{ {
ThrowIfDisposed();
_logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout); _logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
@@ -41,6 +43,7 @@ namespace MQTTnet.Adapter
public Task DisconnectAsync(TimeSpan timeout) public Task DisconnectAsync(TimeSpan timeout)
{ {
ThrowIfDisposed();
_logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout); _logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
@@ -48,6 +51,8 @@ namespace MQTTnet.Adapter
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets) public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
{ {
ThrowIfDisposed();
return ExecuteAndWrapExceptionAsync(async () => return ExecuteAndWrapExceptionAsync(async () =>
{ {
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -55,6 +60,11 @@ namespace MQTTnet.Adapter
{ {
foreach (var packet in packets) foreach (var packet in packets)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null) if (packet == null)
{ {
continue; continue;
@@ -65,10 +75,20 @@ namespace MQTTnet.Adapter
var chunks = PacketSerializer.Serialize(packet); var chunks = PacketSerializer.Serialize(packet);
foreach (var chunk in chunks) foreach (var chunk in chunks)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false); await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
} }
} }
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (timeout > TimeSpan.Zero) if (timeout > TimeSpan.Zero)
{ {
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
@@ -87,6 +107,8 @@ namespace MQTTnet.Adapter
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
ThrowIfDisposed();
MqttBasePacket packet = null; MqttBasePacket packet = null;
await ExecuteAndWrapExceptionAsync(async () => await ExecuteAndWrapExceptionAsync(async () =>
{ {
@@ -214,5 +236,20 @@ namespace MQTTnet.Adapter
throw new MqttCommunicationException(exception); throw new MqttCommunicationException(exception);
} }
} }
public void Dispose()
{
_isDisposed = true;
_semaphore?.Dispose();
_channel?.Dispose();
}
private void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
}
}
} }
} }
@@ -1,9 +1,10 @@
using System.IO; using System;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Channel namespace MQTTnet.Channel
{ {
public interface IMqttChannel public interface IMqttChannel : IDisposable
{ {
Stream SendStream { get; } Stream SendStream { get; }
Stream ReceiveStream { get; } Stream ReceiveStream { get; }
@@ -17,6 +17,7 @@ namespace MQTTnet.Client
{ {
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly Stopwatch _sendTracker = new Stopwatch(); private readonly Stopwatch _sendTracker = new Stopwatch();
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher; private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -24,13 +25,15 @@ namespace MQTTnet.Client
private IMqttClientOptions _options; private IMqttClientOptions _options;
private bool _isReceivingPackets; private bool _isReceivingPackets;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private Task _packetReceiverTask;
private Task _keepAliveMessageSenderTask;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{ {
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); _adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_packetDispatcher = new MqttPacketDispatcher(logger); _packetDispatcher = new MqttPacketDispatcher(logger);
} }
@@ -55,12 +58,12 @@ namespace MQTTnet.Client
_packetDispatcher.Reset(); _packetDispatcher.Reset();
_adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger); _adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger);
_logger.Trace<MqttClient>("Trying to connect with server."); _logger.Trace<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Trace<MqttClient>("Connection with server established."); _logger.Trace<MqttClient>("Connection with server established.");
await StartReceivingPacketsAsync(_cancellationTokenSource.Token).ConfigureAwait(false); await StartReceivingPacketsAsync().ConfigureAwait(false);
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false); var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
_logger.Trace<MqttClient>("MQTT connection with server established."); _logger.Trace<MqttClient>("MQTT connection with server established.");
@@ -69,7 +72,7 @@ namespace MQTTnet.Client
if (_options.KeepAlivePeriod != TimeSpan.Zero) if (_options.KeepAlivePeriod != TimeSpan.Zero)
{ {
StartSendingKeepAliveMessages(_cancellationTokenSource.Token); StartSendingKeepAliveMessages();
} }
IsConnected = true; IsConnected = true;
@@ -94,7 +97,10 @@ namespace MQTTnet.Client
try try
{ {
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); if (!_cancellationTokenSource.IsCancellationRequested)
{
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
}
} }
finally finally
{ {
@@ -144,7 +150,7 @@ namespace MQTTnet.Client
ThrowIfNotConnected(); ThrowIfNotConnected();
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket()); var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key); var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key);
foreach (var qosGroup in packetGroups) foreach (var qosGroup in packetGroups)
{ {
@@ -153,7 +159,7 @@ namespace MQTTnet.Client
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(qosGroup.ToArray()).ConfigureAwait(false); await SendAsync((MqttPublishPacket[])qosGroup.ToArray()).ConfigureAwait(false);
break; break;
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
@@ -194,6 +200,9 @@ namespace MQTTnet.Client
public void Dispose() public void Dispose()
{ {
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_adapter?.Dispose();
} }
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage) private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
@@ -229,23 +238,31 @@ namespace MQTTnet.Client
private async Task DisconnectInternalAsync(Exception exception) private async Task DisconnectInternalAsync(Exception exception)
{ {
await _disconnectLock.WaitAsync();
var clientWasConnected = IsConnected; var clientWasConnected = IsConnected;
IsConnected = false;
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
cts.Cancel(false);
cts.Dispose();
_cancellationTokenSource = null;
try try
{ {
IsConnected = false;
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
{
return;
}
_cancellationTokenSource.Cancel(false);
if (_packetReceiverTask != null)
{
Task.WaitAll(_packetReceiverTask);
}
if (_keepAliveMessageSenderTask != null)
{
Task.WaitAll(_keepAliveMessageSenderTask);
}
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Info<MqttClient>("Disconnected from adapter."); _logger.Trace<MqttClient>("Disconnected from adapter.");
} }
catch (Exception adapterException) catch (Exception adapterException)
{ {
@@ -253,6 +270,14 @@ namespace MQTTnet.Client
} }
finally finally
{ {
_adapter?.Dispose();
_adapter = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_disconnectLock.Release();
_logger.Info<MqttClient>("Disconnected."); _logger.Info<MqttClient>("Disconnected.");
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
} }
@@ -263,7 +288,7 @@ namespace MQTTnet.Client
try try
{ {
_logger.Info<MqttClient>("Received <<< {0}", packet); _logger.Info<MqttClient>("Received <<< {0}", packet);
if (packet is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
{ {
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false); await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false);
@@ -311,6 +336,11 @@ namespace MQTTnet.Client
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket) private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
{ {
if (_cancellationTokenSource.IsCancellationRequested)
{
return Task.FromResult(0);
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
FireApplicationMessageReceivedEvent(publishPacket); FireApplicationMessageReceivedEvent(publishPacket);
@@ -363,54 +393,48 @@ namespace MQTTnet.Client
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
} }
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) private async Task SendKeepAliveMessagesAsync()
{ {
_logger.Info<MqttClient>("Start sending keep alive packets."); _logger.Info<MqttClient>("Start sending keep alive packets.");
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!_cancellationTokenSource.Token.IsCancellationRequested)
{ {
TimeSpan keepAliveSendInterval; var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
if (_options.KeepAliveSendInterval.HasValue) if (_options.KeepAliveSendInterval.HasValue)
{ {
keepAliveSendInterval = _options.KeepAliveSendInterval.Value; keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
} }
else
{
keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
}
if (_sendTracker.Elapsed > keepAliveSendInterval) if (_sendTracker.Elapsed > keepAliveSendInterval)
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
} }
await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false); await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync(null).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets."); if (_cancellationTokenSource.Token.IsCancellationRequested)
{
return;
}
if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
}
else
{
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(exception).ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
finally finally
@@ -419,48 +443,46 @@ namespace MQTTnet.Client
} }
} }
private async Task ReceivePacketsAsync(CancellationToken cancellationToken) private async Task ReceivePacketsAsync()
{ {
_logger.Info<MqttClient>("Start receiving packets."); _logger.Info<MqttClient>("Start receiving packets.");
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!_cancellationTokenSource.Token.IsCancellationRequested)
{ {
_isReceivingPackets = true; _isReceivingPackets = true;
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested) if (_cancellationTokenSource.Token.IsCancellationRequested)
{ {
return; return;
} }
StartProcessReceivedPacket(packet, cancellationToken); StartProcessReceivedPacket(packet);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync(null).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets."); if (_cancellationTokenSource.IsCancellationRequested)
{
return;
}
if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
}
else
{
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
}
await DisconnectInternalAsync(exception).ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
finally finally
@@ -469,38 +491,26 @@ namespace MQTTnet.Client
} }
} }
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken) private void StartProcessReceivedPacket(MqttBasePacket packet)
{ {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
Task.Run(
async () => await ProcessReceivedPacketAsync(packet).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
private async Task StartReceivingPacketsAsync(CancellationToken cancellationToken) private async Task StartReceivingPacketsAsync()
{ {
_isReceivingPackets = false; _isReceivingPackets = false;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
Task.Run(
async () => await ReceivePacketsAsync(cancellationToken).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
while (!_isReceivingPackets && !cancellationToken.IsCancellationRequested) while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
{ {
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false); await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) private void StartSendingKeepAliveMessages()
{ {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
Task.Run(
async () => await SendKeepAliveMessagesAsync(cancellationToken).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
} }
} }
@@ -13,7 +13,7 @@ using MQTTnet.Client;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttChannel, IDisposable public sealed class MqttTcpChannel : IMqttChannel
{ {
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
@@ -12,9 +12,8 @@ using MQTTnet.Client;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttChannel, IDisposable public sealed class MqttTcpChannel : IMqttChannel
{ {
//todo: this can be used with min dependency NetStandard1.6
#if NET452 || NET461 #if NET452 || NET461
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
@@ -63,7 +62,6 @@ namespace MQTTnet.Implementations
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
} }
//todo: else brach can be used with min dependency NET46
#if NET452 || NET461 #if NET452 || NET461
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
#else #else
@@ -73,7 +71,7 @@ namespace MQTTnet.Implementations
if (_options.TlsOptions.UseTls) if (_options.TlsOptions.UseTls)
{ {
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
} }
CreateStreams(); CreateStreams();
@@ -184,15 +182,15 @@ namespace MQTTnet.Implementations
return _options.TlsOptions.AllowUntrustedCertificates; return _options.TlsOptions.AllowUntrustedCertificates;
} }
private static X509CertificateCollection LoadCertificates(MqttClientTcpOptions options) private X509CertificateCollection LoadCertificates()
{ {
var certificates = new X509CertificateCollection(); var certificates = new X509CertificateCollection();
if (options.TlsOptions.Certificates == null) if (_options.TlsOptions.Certificates == null)
{ {
return certificates; return certificates;
} }
foreach (var certificate in options.TlsOptions.Certificates) foreach (var certificate in _options.TlsOptions.Certificates)
{ {
certificates.Add(new X509Certificate2(certificate)); certificates.Add(new X509Certificate2(certificate));
} }
@@ -212,7 +210,6 @@ namespace MQTTnet.Implementations
stream = new NetworkStream(_socket, true); stream = new NetworkStream(_socket, true);
} }
//todo: if branch can be used with min dependency NetStandard1.6
#if NET452 || NET461 #if NET452 || NET461
SendStream = new BufferedStream(stream, _bufferSize); SendStream = new BufferedStream(stream, _bufferSize);
ReceiveStream = new BufferedStream(stream, _bufferSize); ReceiveStream = new BufferedStream(stream, _bufferSize);
@@ -24,6 +24,11 @@ namespace MQTTnet.Serializer
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken) public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested)
{
return null;
}
// Wait for the next package which starts with the header. At this point there will probably // Wait for the next package which starts with the header. At this point there will probably
// some large delay and thus the thread should be put back to the pool (await). So ReadByte() // some large delay and thus the thread should be put back to the pool (await). So ReadByte()
// is not an option here. // is not an option here.
@@ -16,6 +16,8 @@ namespace MQTTnet.Server
private readonly Func<Task> _timeoutCallback; private readonly Func<Task> _timeoutCallback;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private Task _workerTask;
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger) public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
{ {
_clientId = clientId; _clientId = clientId;
@@ -34,7 +36,15 @@ namespace MQTTnet.Server
return; return;
} }
Task.Run(async () => await RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); _workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken);
}
public void WaitForCompletion()
{
if (_workerTask != null)
{
Task.WaitAll(_workerTask);
}
} }
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -18,6 +19,8 @@ namespace MQTTnet.Server
private readonly MqttClientSession _clientSession; private readonly MqttClientSession _clientSession;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private Task _workerTask;
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger) public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
@@ -36,7 +39,15 @@ namespace MQTTnet.Server
return; return;
} }
Task.Run(async () => await SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false); _workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
}
public void WaitForCompletion()
{
if (_workerTask != null)
{
Task.WaitAll(_workerTask);
}
} }
public void Enqueue(MqttBasePacket packet) public void Enqueue(MqttBasePacket packet)
@@ -55,7 +66,7 @@ namespace MQTTnet.Server
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await SendQueuedPacketAsync(adapter, cancellationToken); await SendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -67,7 +78,7 @@ namespace MQTTnet.Server
} }
} }
private async Task SendQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
MqttBasePacket packet = null; MqttBasePacket packet = null;
try try
@@ -78,6 +89,11 @@ namespace MQTTnet.Server
throw new InvalidOperationException(); // should not happen throw new InvalidOperationException(); // should not happen
} }
if (cancellationToken.IsCancellationRequested)
{
return;
}
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); _logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
@@ -110,7 +126,10 @@ namespace MQTTnet.Server
} }
} }
await _clientSession.StopAsync(); if (!cancellationToken.IsCancellationRequested)
{
await _clientSession.StopAsync().ConfigureAwait(false);
}
} }
} }
@@ -96,14 +96,14 @@ namespace MQTTnet.Server
} }
_cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Cancel(false);
PendingMessagesQueue.WaitForCompletion();
KeepAliveMonitor.WaitForCompletion();
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null; _cancellationTokenSource = null;
if (_adapter != null) _adapter = null;
{
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
_adapter = null;
}
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId); _logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
} }
@@ -33,16 +33,18 @@ namespace MQTTnet.Server
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; } public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; }
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; } public Action<string, string> ClientUnsubscribedTopicCallback { get; set; }
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; } public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; }
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
{ {
var clientId = string.Empty; var clientId = string.Empty;
MqttClientSession clientSession = null; MqttClientSession clientSession = null;
try try
{ {
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket)) if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
.ConfigureAwait(false) is MqttConnectPacket connectPacket))
{ {
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); throw new MqttProtocolViolationException(
"The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
} }
clientId = connectPacket.ClientId; clientId = connectPacket.ClientId;
@@ -53,10 +55,13 @@ namespace MQTTnet.Server
var connectReturnCode = ValidateConnection(connectPacket); var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{ {
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
{ {
ConnectReturnCode = connectReturnCode new MqttConnAckPacket
}}).ConfigureAwait(false); {
ConnectReturnCode = connectReturnCode
}
}).ConfigureAwait(false);
return; return;
} }
@@ -64,11 +69,14 @@ namespace MQTTnet.Server
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
clientSession = result.Session; clientSession = result.Session;
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
{ {
ConnectReturnCode = connectReturnCode, new MqttConnAckPacket
IsSessionPresent = result.IsExistingSession {
}}).ConfigureAwait(false); ConnectReturnCode = connectReturnCode,
IsSessionPresent = result.IsExistingSession
}
}).ConfigureAwait(false);
ClientConnectedCallback?.Invoke(new ConnectedMqttClient ClientConnectedCallback?.Invoke(new ConnectedMqttClient
{ {
@@ -78,6 +86,9 @@ namespace MQTTnet.Server
await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
} }
catch (OperationCanceledException)
{
}
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientSessionsManager>(exception, exception.Message); _logger.Error<MqttClientSessionsManager>(exception, exception.Message);
@@ -87,12 +98,13 @@ namespace MQTTnet.Server
try try
{ {
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
clientAdapter.Dispose();
} }
catch (Exception) catch (Exception exception)
{ {
// ignored _logger.Error<MqttClientSessionsManager>(exception, exception.Message);
} }
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
{ {
ClientId = clientId, ClientId = clientId,
@@ -308,7 +320,7 @@ namespace MQTTnet.Server
ClientSubscribedTopicCallback = null; ClientSubscribedTopicCallback = null;
ClientUnsubscribedTopicCallback = null; ClientUnsubscribedTopicCallback = null;
ApplicationMessageReceivedCallback = null; ApplicationMessageReceivedCallback = null;
_semaphore?.Dispose(); _semaphore?.Dispose();
} }
} }
@@ -17,6 +17,10 @@ namespace MQTTnet.Core.Tests
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer(); public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
public void Dispose()
{
}
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout)
{ {
return Task.FromResult(0); return Task.FromResult(0);
+1 -1
View File
@@ -89,7 +89,7 @@ namespace MQTTnet.TestApp.NetCore
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString());
} }
} }
catch (Exception e) catch (Exception)
{ {
} }
}; };