Refactor message processing and async/await usage.

This commit is contained in:
Christian Kratky
2018-06-16 14:30:02 +02:00
parent 6192685671
commit caea7910b4
31 changed files with 605 additions and 653 deletions
@@ -11,7 +11,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttClientPendingMessagesQueue : IDisposable
public class MqttClientPendingPacketsQueue : IDisposable
{
private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent();
private readonly IMqttServerOptions _options;
@@ -20,13 +20,13 @@ namespace MQTTnet.Server
private ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger)
public MqttClientPendingPacketsQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
_logger = logger.CreateChildLogger(nameof(MqttClientPendingMessagesQueue));
_logger = logger.CreateChildLogger(nameof(MqttClientPendingPacketsQueue));
}
public int Count => _queue.Count;
@@ -115,7 +115,7 @@ namespace MQTTnet.Server
return;
}
await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, packet, cancellationToken).ConfigureAwait(false);
await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
_logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
}
+19 -22
View File
@@ -18,7 +18,7 @@ namespace MQTTnet.Server
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
private readonly MqttClientPendingPacketsQueue _pendingPacketsQueue;
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
private readonly MqttClientSessionsManager _sessionsManager;
@@ -49,7 +49,7 @@ namespace MQTTnet.Server
_keepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, () => Stop(MqttClientDisconnectType.NotClean), _logger);
_subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
_pendingPacketsQueue = new MqttClientPendingPacketsQueue(_options, this, _logger);
}
public string ClientId { get; }
@@ -60,7 +60,7 @@ namespace MQTTnet.Server
status.IsConnected = _adapter != null;
status.Endpoint = _adapter?.Endpoint;
status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion;
status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count;
status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count;
status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived;
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
}
@@ -80,7 +80,7 @@ namespace MQTTnet.Server
_wasCleanDisconnect = false;
_willMessage = connectPacket.WillMessage;
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
_pendingPacketsQueue.Start(adapter, _cancellationTokenSource.Token);
_keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
while (!_cancellationTokenSource.IsCancellationRequested)
@@ -149,13 +149,10 @@ namespace MQTTnet.Server
if (_willMessage != null && !_wasCleanDisconnect)
{
_sessionsManager.StartDispatchApplicationMessage(this, _willMessage);
_sessionsManager.EnqueueApplicationMessage(this, _willMessage);
}
_willMessage = null;
////_pendingMessagesQueue.WaitForCompletion();
////_keepAliveMonitor.WaitForCompletion();
}
finally
{
@@ -196,7 +193,7 @@ namespace MQTTnet.Server
}
}
_pendingMessagesQueue.Enqueue(publishPacket);
_pendingPacketsQueue.Enqueue(publishPacket);
}
public Task SubscribeAsync(IList<TopicFilter> topicFilters)
@@ -226,12 +223,12 @@ namespace MQTTnet.Server
public void ClearPendingApplicationMessages()
{
_pendingMessagesQueue.Clear();
_pendingPacketsQueue.Clear();
}
public void Dispose()
{
_pendingMessagesQueue?.Dispose();
_pendingPacketsQueue?.Dispose();
_cancellationTokenSource?.Dispose();
}
@@ -245,7 +242,7 @@ namespace MQTTnet.Server
if (packet is MqttPingReqPacket)
{
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, new MqttPingRespPacket(), cancellationToken);
return adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken);
}
if (packet is MqttPubRelPacket pubRelPacket)
@@ -260,7 +257,7 @@ namespace MQTTnet.Server
PacketIdentifier = pubRecPacket.PacketIdentifier
};
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, responsePacket, cancellationToken);
return adapter.SendPacketAsync(responsePacket, cancellationToken);
}
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
@@ -308,7 +305,7 @@ namespace MQTTnet.Server
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket);
await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false);
await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false);
if (subscribeResult.CloseConnection)
{
@@ -322,7 +319,7 @@ namespace MQTTnet.Server
private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket);
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, unsubscribeResult, cancellationToken);
return adapter.SendPacketAsync(unsubscribeResult, cancellationToken);
}
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
@@ -333,7 +330,7 @@ namespace MQTTnet.Server
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
_sessionsManager.EnqueueApplicationMessage(this, applicationMessage);
return Task.FromResult(0);
}
case MqttQualityOfServiceLevel.AtLeastOnce:
@@ -353,25 +350,25 @@ namespace MQTTnet.Server
private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
_sessionsManager.EnqueueApplicationMessage(this, applicationMessage);
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken);
return adapter.SendPacketAsync(response, cancellationToken);
}
private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
_sessionsManager.EnqueueApplicationMessage(this, applicationMessage);
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken);
return adapter.SendPacketAsync(response, cancellationToken);
}
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
private static Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken);
return adapter.SendPacketAsync(response, cancellationToken);
}
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
+147 -131
View File
@@ -6,27 +6,29 @@ using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttClientSessionsManager : IDisposable
public class MqttClientSessionsManager
{
private readonly BlockingCollection<MqttEnqueuedApplicationMessage> _messageQueue = new BlockingCollection<MqttEnqueuedApplicationMessage>();
private readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
private readonly AsyncLock _sessionPreparationLock = new AsyncLock();
private readonly CancellationToken _cancellationToken;
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly IMqttServerOptions _options;
private readonly IMqttNetChildLogger _logger;
public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger)
public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, CancellationToken cancellationToken, IMqttNetChildLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateChildLogger(nameof(MqttClientSessionsManager));
_cancellationToken = cancellationToken;
_options = options ?? throw new ArgumentNullException(nameof(options));
Server = server ?? throw new ArgumentNullException(nameof(server));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
@@ -34,83 +36,9 @@ namespace MQTTnet.Server
public MqttServer Server { get; }
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
public void Start()
{
var clientId = string.Empty;
var wasCleanDisconnect = false;
try
{
var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false);
if (firstPacket == null)
{
return;
}
if (!(firstPacket is MqttConnectPacket connectPacket))
{
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
}
clientId = connectPacket.ClientId;
// Switch to the required protocol version before sending any response.
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout,
new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode
},
cancellationToken).ConfigureAwait(false);
return;
}
var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
var clientSession = result.Session;
await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout,
new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode,
IsSessionPresent = result.IsExistingSession
},
cancellationToken).ConfigureAwait(false);
Server.OnClientConnected(clientId);
wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
finally
{
try
{
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
clientAdapter.Dispose();
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
if (!_options.EnablePersistentSessions)
{
DeleteSession(clientId);
}
Server.OnClientDisconnected(clientId, wasCleanDisconnect);
}
Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public Task StopAsync()
@@ -124,6 +52,11 @@ namespace MQTTnet.Server
return Task.FromResult(0);
}
public Task StartSession(IMqttChannelAdapter clientAdapter)
{
return Task.Run(() => RunSession(clientAdapter, _cancellationToken), _cancellationToken);
}
public Task<IList<IMqttClientSessionStatus>> GetClientStatusAsync()
{
var result = new List<IMqttClientSessionStatus>();
@@ -138,9 +71,11 @@ namespace MQTTnet.Server
return Task.FromResult((IList<IMqttClientSessionStatus>)result);
}
public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{
Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
_messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken);
}
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
@@ -175,9 +110,131 @@ namespace MQTTnet.Server
_logger.Verbose("Session for client '{0}' deleted.", clientId);
}
public void Dispose()
private void ProcessQueuedApplicationMessages(CancellationToken cancellationToken)
{
_sessionPreparationLock?.Dispose();
while (!cancellationToken.IsCancellationRequested)
{
try
{
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken);
var sender = enqueuedApplicationMessage.Sender;
var applicationMessage = enqueuedApplicationMessage.ApplicationMessage;
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage);
if (interceptorContext != null)
{
if (interceptorContext.CloseConnection)
{
enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
applicationMessage = interceptorContext.ApplicationMessage;
}
Server.OnApplicationMessageReceived(sender?.ClientId, applicationMessage);
if (applicationMessage.Retain)
{
_retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult();
}
foreach (var clientSession in _sessions.Values)
{
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage);
}
}
catch (TaskCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Unhandled exception while processing queued application message.");
}
}
}
private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
{
var clientId = string.Empty;
var wasCleanDisconnect = false;
try
{
var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false);
if (firstPacket == null)
{
return;
}
if (!(firstPacket is MqttConnectPacket connectPacket))
{
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
}
clientId = connectPacket.ClientId;
// Switch to the required protocol version before sending any response.
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
await clientAdapter.SendPacketAsync(
new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode
},
cancellationToken).ConfigureAwait(false);
return;
}
var result = PrepareClientSession(connectPacket);
var clientSession = result.Session;
await clientAdapter.SendPacketAsync(
new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode,
IsSessionPresent = result.IsExistingSession
},
cancellationToken).ConfigureAwait(false);
Server.OnClientConnected(clientId);
wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
finally
{
try
{
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
clientAdapter.Dispose();
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
if (!_options.EnablePersistentSessions)
{
DeleteSession(clientId);
}
Server.OnClientDisconnected(clientId, wasCleanDisconnect);
}
}
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
@@ -197,9 +254,9 @@ namespace MQTTnet.Server
return context.ReturnCode;
}
private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
private PrepareClientSessionResult PrepareClientSession(MqttConnectPacket connectPacket)
{
using (await _sessionPreparationLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
lock (_sessions)
{
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
if (isSessionPresent)
@@ -231,60 +288,19 @@ namespace MQTTnet.Server
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
}
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
return new PrepareClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
}
}
private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage)
{
try
{
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
if (interceptorContext != null)
{
if (interceptorContext.CloseConnection)
{
senderClientSession.Stop(MqttClientDisconnectType.NotClean);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
applicationMessage = interceptorContext.ApplicationMessage;
}
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
if (applicationMessage.Retain)
{
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
}
foreach (var clientSession in _sessions.Values)
{
clientSession.EnqueueApplicationMessage(senderClientSession, applicationMessage);
}
}
catch (Exception exception)
{
_logger.Error(exception, "Error while processing application message");
}
}
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{
var interceptorContext = new MqttApplicationMessageInterceptorContext(
senderClientSession?.ClientId,
applicationMessage);
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{
return interceptorContext;
return null;
}
var interceptorContext = new MqttApplicationMessageInterceptorContext(sender?.ClientId, applicationMessage);
interceptor(interceptorContext);
return interceptorContext;
}
@@ -0,0 +1,15 @@
namespace MQTTnet.Server
{
public class MqttEnqueuedApplicationMessage
{
public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage)
{
Sender = sender;
ApplicationMessage = applicationMessage;
}
public MqttClientSession Sender { get; }
public MqttApplicationMessage ApplicationMessage { get; }
}
}
+4 -7
View File
@@ -65,7 +65,7 @@ namespace MQTTnet.Server
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started.");
_clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage);
_clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage);
return Task.FromResult(0);
}
@@ -81,7 +81,8 @@ namespace MQTTnet.Server
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false);
_clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger);
_clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _cancellationTokenSource.Token, _logger);
_clientSessionsManager.Start();
foreach (var adapter in _adapters)
{
@@ -118,8 +119,6 @@ namespace MQTTnet.Server
}
finally
{
_clientSessionsManager?.Dispose();
_cancellationTokenSource = null;
_retainedMessagesManager = null;
_clientSessionsManager = null;
@@ -155,9 +154,7 @@ namespace MQTTnet.Server
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
{
eventArgs.SessionTask = Task.Run(
() => _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token),
_cancellationTokenSource.Token);
eventArgs.SessionTask = _clientSessionsManager.StartSession(eventArgs.Client);
}
}
}
@@ -1,6 +1,6 @@
namespace MQTTnet.Server
{
public class GetOrCreateClientSessionResult
public class PrepareClientSessionResult
{
public bool IsExistingSession { get; set; }