Implement new managed client.

This commit is contained in:
Christian
2022-09-29 18:38:29 +02:00
parent e050e2d61e
commit 7c3fad6147
42 changed files with 1723 additions and 401 deletions
@@ -0,0 +1,95 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ApplicationMessagesManager
{
readonly MqttNetSourceLogger _logger;
readonly Queue<ManagedMqttApplicationMessage> _messageQueue = new Queue<ManagedMqttApplicationMessage>();
readonly IMqttClient _mqttClient;
public ApplicationMessagesManager(IMqttClient mqttClient, IMqttNetLogger logger)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource<ApplicationMessagesManager>();
}
public AsyncEvent<ApplicationMessageDroppedEventArgs> ApplicationMessageDroppedEvent { get; } = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
public AsyncEvent<ApplicationMessageEnqueueingEventArgs> ApplicationMessageEnqueueingEvent { get; } = new AsyncEvent<ApplicationMessageEnqueueingEventArgs>();
public int EnqueuedApplicationMessagesCount
{
get
{
lock (_messageQueue)
{
return _messageQueue.Count;
}
}
}
public void Dequeue()
{
lock (_messageQueue)
{
_messageQueue.Dequeue();
}
}
public async Task Enqueue(ManagedMqttApplicationMessage applicationMessage)
{
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
// Fire the event first so that the handler can make sure that this message is
// properly backed up in a suitable storage.
if (ApplicationMessageEnqueueingEvent.HasHandlers)
{
var eventArgs = new ApplicationMessageEnqueueingEventArgs(applicationMessage, 0);
await ApplicationMessageEnqueueingEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
lock (_messageQueue)
{
// TODO: Drop if too much!
_messageQueue.Enqueue(applicationMessage);
}
}
public bool TryPeek(out ManagedMqttApplicationMessage applicationMessage)
{
lock (_messageQueue)
{
if (_messageQueue.Count == 0)
{
applicationMessage = null;
return false;
}
applicationMessage = _messageQueue.Peek();
return true;
}
}
}
}
@@ -0,0 +1,18 @@
// 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.Extensions.ManagedClient
{
public sealed class ApplicationMessageDroppedEventArgs : EventArgs
{
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -0,0 +1,21 @@
// 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.Extensions.ManagedClient
{
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
{
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PacketIdentifier = packetIdentifier;
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
public ushort PacketIdentifier { get; }
}
}
@@ -0,0 +1,26 @@
// 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.Extensions.ManagedClient
{
public sealed class ApplicationMessageProcessedEventArgs : EventArgs
{
public ApplicationMessageProcessedEventArgs(ManagedMqttApplicationMessage applicationMessage, Exception exception)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
Exception = exception;
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets or sets the exception which was thrown while sending the message.
/// </summary>
public Exception Exception { get; }
public bool HasFailed => Exception != null;
}
}
@@ -0,0 +1,25 @@
// 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 MQTTnet.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ConnectingFailedEventArgs : EventArgs
{
public ConnectingFailedEventArgs(MqttClientConnectResult connectResult, Exception exception)
{
ConnectResult = connectResult;
Exception = exception;
}
/// <summary>
/// This is null when the server was not reachable and thus no MQTT response was transmitted.
/// </summary>
public MqttClientConnectResult ConnectResult { get; }
public Exception Exception { get; }
}
}
@@ -0,0 +1,18 @@
// 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.Extensions.ManagedClient
{
public class ManagedProcessFailedEventArgs : EventArgs
{
public ManagedProcessFailedEventArgs(Exception exception)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
}
public Exception Exception { get; }
}
}
@@ -0,0 +1,27 @@
// 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.Collections.Generic;
using MQTTnet.Client;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class SubscribeProcessedEventArgs : EventArgs
{
public SubscribeProcessedEventArgs(MqttClientSubscribeOptions options, MqttClientSubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
Result = result;
UserProperties = userProperties;
}
public MqttClientSubscribeOptions Options { get; }
public MqttClientSubscribeResultCode Result { get; }
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
}
}
@@ -0,0 +1,27 @@
// 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.Collections.Generic;
using MQTTnet.Client;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class UnsubscribeProcessedEventArgs : EventArgs
{
public UnsubscribeProcessedEventArgs(MqttClientUnsubscribeOptions options, MqttClientUnsubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
Result = result;
UserProperties = userProperties;
}
public MqttClientUnsubscribeOptions Options { get; }
public MqttClientUnsubscribeResultCode Result { get; }
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
}
}
@@ -0,0 +1,15 @@
// 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.
namespace MQTTnet.Extensions.ManagedClient
{
public enum MaintainConnectionResult
{
NoChange,
ConnectedEstablished,
NotConnected
}
}
@@ -0,0 +1,35 @@
// 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.Linq;
using MQTTnet.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttSubscription
{
public ManagedMqttSubscription(MqttClientSubscribeOptions subscribeOptions)
{
SubscribeOptions = subscribeOptions ?? throw new ArgumentNullException(nameof(subscribeOptions));
Topic = subscribeOptions.TopicFilters.First().Topic;
}
public bool IsObsolete => UnsubscribeOptions != null;
public bool IsPending { get; set; } = true;
public MqttClientSubscribeOptions SubscribeOptions { get; }
public string Topic { get; }
public MqttClientUnsubscribeOptions UnsubscribeOptions { get; private set; }
public void MarkAsUnsubscribed(MqttClientUnsubscribeOptions options)
{
UnsubscribeOptions = options ?? throw new ArgumentNullException(nameof(options));
}
}
}
@@ -0,0 +1,155 @@
// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttSubscriptionsManager
{
readonly MqttNetSourceLogger _logger;
readonly IMqttClient _mqttClient;
// Do not use a dictionary in order to preserve subscription ordering (retained messages).
readonly List<ManagedMqttSubscription> _subscriptions = new List<ManagedMqttSubscription>();
public ManagedMqttSubscriptionsManager(IMqttClient mqttClient, IMqttNetLogger logger)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource<ManagedMqttSubscriptionsManager>();
}
public AsyncEvent<SubscribeProcessedEventArgs> SubscribeProcessedEvent { get; } = new AsyncEvent<SubscribeProcessedEventArgs>();
public AsyncEvent<UnsubscribeProcessedEventArgs> UnsubscribeProcessedEvent { get; } = new AsyncEvent<UnsubscribeProcessedEventArgs>();
public void ResubscribeAll()
{
lock (_subscriptions)
{
foreach (var subscription in _subscriptions)
{
subscription.IsPending = true;
}
}
}
public void Subscribe(MqttClientSubscribeOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.TopicFilters.Count > 1)
{
throw new NotSupportedException("The managed client only supports subscribing to one topic at the same time.");
}
var topic = options.TopicFilters.First().Topic;
lock (_subscriptions)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topic);
_subscriptions.RemoveAll(s => s.Topic.Equals(topic));
_subscriptions.Add(new ManagedMqttSubscription(options));
}
}
public async Task Synchronize(CancellationToken cancellationToken)
{
// TODO: Remember real changes.
List<ManagedMqttSubscription> pendingSubscriptions;
List<ManagedMqttSubscription> obsoleteSubscriptions;
lock (_subscriptions)
{
pendingSubscriptions = _subscriptions.Where(s => s.IsPending).ToList();
obsoleteSubscriptions = _subscriptions.Where(s => s.IsObsolete).ToList();
}
if (pendingSubscriptions.Count == 0 && obsoleteSubscriptions.Count == 0)
{
_logger.Verbose("No subscriptions to synchronize");
return;
}
foreach (var subscription in pendingSubscriptions)
{
var subscribeResult = await _mqttClient.SubscribeAsync(subscription.SubscribeOptions, cancellationToken).ConfigureAwait(false);
subscription.IsPending = false;
await FireSubscribeProcessedEvent(subscription, subscribeResult).ConfigureAwait(false);
}
foreach (var subscription in obsoleteSubscriptions)
{
var unsubscribeResult = await _mqttClient.UnsubscribeAsync(subscription.UnsubscribeOptions, cancellationToken).ConfigureAwait(false);
lock (_subscriptions)
{
_subscriptions.Remove(subscription);
}
await FireUnsubscribeProcessedEvent(subscription, unsubscribeResult).ConfigureAwait(false);
}
}
public void Unsubscribe(MqttClientUnsubscribeOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
lock (_subscriptions)
{
var topic = options.TopicFilters.First();
var existingSubscription = _subscriptions.FirstOrDefault(s => s.Topic.Equals(topic));
if (existingSubscription != null)
{
existingSubscription.MarkAsUnsubscribed(options);
}
}
}
async Task FireSubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientSubscribeResult subscribeResult)
{
_logger.Info("Subscribed topic '{0}'.", subscription.Topic);
if (SubscribeProcessedEvent.HasHandlers)
{
var resultCode = subscribeResult?.Items.First().ResultCode ?? MqttClientSubscribeResultCode.UnspecifiedError;
var eventArgs = new SubscribeProcessedEventArgs(subscription.SubscribeOptions, resultCode, subscribeResult?.UserProperties);
await SubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
async Task FireUnsubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientUnsubscribeResult unsubscribeResult)
{
_logger.Info("Unsubscribed topic '{0}'.", subscription.Topic);
if (UnsubscribeProcessedEvent.HasHandlers)
{
var resultCode = unsubscribeResult?.Items.First().ResultCode ?? MqttClientUnsubscribeResultCode.UnspecifiedError;
var eventArgs = new UnsubscribeProcessedEventArgs(subscription.UnsubscribeOptions, resultCode, unsubscribeResult?.UserProperties);
await UnsubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
}
@@ -0,0 +1,103 @@
// 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;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class MqttConnectionManager
{
readonly MqttNetSourceLogger _logger;
readonly IMqttClient _mqttClient;
bool _wasConnected;
public MqttConnectionManager(IMqttClient mqttClient, IMqttNetLogger logger)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource<MqttConnectionManager>();
_mqttClient.DisconnectedAsync += OnClientDisconnected;
}
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
public AsyncEvent<ConnectingFailedEventArgs> ConnectingFailedEvent { get; } = new AsyncEvent<ConnectingFailedEventArgs>();
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
public async Task<MaintainConnectionResult> MaintainConnection(ManagedMqttClientOptions options, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_wasConnected)
{
try
{
// The only valid way for testing the connection is to send a PING. So we start with this
// in order to check if reconnecting etc. is require or not.
await _mqttClient.PingAsync(cancellationToken).ConfigureAwait(false);
// The client is properly connection. No further action required.
return MaintainConnectionResult.NoChange;
}
catch (Exception exception)
{
_logger.Error(exception, "Error while checking connection.");
}
}
MqttClientConnectResult connectResult = null;
try
{
// The PING was not successful so that there is no working connection. So we connect.
connectResult = await _mqttClient.ConnectAsync(options.ClientOptions, cancellationToken).ConfigureAwait(false);
_wasConnected = true;
_logger.Info("Connected.");
if (ConnectedEvent.HasHandlers)
{
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
await ConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
}
return MaintainConnectionResult.ConnectedEstablished;
}
catch (Exception exception)
{
if (!(exception is OperationCanceledException))
{
_logger.Error(exception, "Error while connecting.");
}
if (ConnectingFailedEvent.HasHandlers)
{
var eventArgs = new ConnectingFailedEventArgs(connectResult, exception);
await ConnectingFailedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
_wasConnected = false;
return MaintainConnectionResult.NotConnected;
}
Task OnClientDisconnected(MqttClientDisconnectedEventArgs arg)
{
_wasConnected = false;
return DisconnectedEvent?.InvokeAsync(arg);
}
}
}
@@ -0,0 +1,310 @@
// 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;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class NewManagedMqttClient : Disposable, IManagedMqttClient
{
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
readonly ApplicationMessagesManager _applicationMessagesManager;
readonly MqttConnectionManager _connectionManager;
readonly AsyncEvent<EventArgs> _isConnectedChangedEvent = new AsyncEvent<EventArgs>();
readonly MqttNetSourceLogger _logger;
readonly ManagedMqttSubscriptionsManager _subscriptionsManager;
readonly ManualResetEventSlim _workSignal = new ManualResetEventSlim();
CancellationTokenSource _workerCancellationToken;
Task _workerTask;
public NewManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
InternalClient.DisconnectedAsync += OnInternalClientDisconnected;
_logger = logger.WithSource(nameof(NewManagedMqttClient));
_connectionManager = new MqttConnectionManager(mqttClient, logger);
_subscriptionsManager = new ManagedMqttSubscriptionsManager(mqttClient, logger);
_applicationMessagesManager = new ApplicationMessagesManager(mqttClient, logger);
// TODO: Start timer for periodical connection checking.
}
public event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync
{
add => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.AddHandler(value);
remove => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.RemoveHandler(value);
}
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
{
add => _applicationMessageProcessedEvent.AddHandler(value);
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => InternalClient.ApplicationMessageReceivedAsync += value;
remove => InternalClient.ApplicationMessageReceivedAsync += value;
}
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync
{
add => _applicationMessagesManager.ApplicationMessageDroppedEvent.AddHandler(value);
remove => _applicationMessagesManager.ApplicationMessageDroppedEvent.RemoveHandler(value);
}
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{
add => _connectionManager.ConnectedEvent.AddHandler(value);
remove => _connectionManager.ConnectedEvent.RemoveHandler(value);
}
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
{
add => _connectionManager.ConnectingFailedEvent.AddHandler(value);
remove => _connectionManager.ConnectingFailedEvent.RemoveHandler(value);
}
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
{
add => _connectionManager.DisconnectedEvent.AddHandler(value);
remove => _connectionManager.DisconnectedEvent.RemoveHandler(value);
}
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync
{
add => _subscriptionsManager.SubscribeProcessedEvent.AddHandler(value);
remove => _subscriptionsManager.SubscribeProcessedEvent.RemoveHandler(value);
}
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync
{
add => _subscriptionsManager.UnsubscribeProcessedEvent.AddHandler(value);
remove => _subscriptionsManager.UnsubscribeProcessedEvent.RemoveHandler(value);
}
public IMqttClient InternalClient { get; }
public bool IsConnected { get; private set; }
public bool IsStarted { get; private set; }
public ManagedMqttClientOptions Options { get; private set; }
public int EnqueuedApplicationMessagesCount => _applicationMessagesManager.EnqueuedApplicationMessagesCount;
public Task EnqueueAsync(MqttApplicationMessage applicationMessage)
{
return EnqueueAsync(new ManagedMqttApplicationMessage(Guid.NewGuid().ToString(), applicationMessage));
}
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
{
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
await _applicationMessagesManager.Enqueue(applicationMessage).ConfigureAwait(false);
_workSignal.Set();
}
public Task PingAsync(CancellationToken cancellationToken = default)
{
return InternalClient.PingAsync(cancellationToken);
}
public Task StartAsync(ManagedMqttClientOptions options)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
_workerCancellationToken = new CancellationTokenSource();
_workerTask = Task.Factory.StartNew(
() => WorkerLoop(_workerCancellationToken.Token),
_workerCancellationToken.Token,
TaskCreationOptions.PreferFairness,
TaskScheduler.Default);
_workSignal.Set();
return PlatformAbstractionLayer.CompletedTask;
}
public async Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// TODO: Send proper Disconnect signal.
try
{
await InternalClient.DisconnectAsync(options, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while disconnecting.");
}
_workerCancellationToken?.Cancel(false);
_workerCancellationToken?.Dispose();
_workerCancellationToken = null;
if (_workerTask != null)
{
}
}
public Task SubscribeAsync(MqttClientSubscribeOptions options)
{
ThrowIfDisposed();
_subscriptionsManager.Subscribe(options);
_workSignal.Set();
return PlatformAbstractionLayer.CompletedTask;
}
public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options)
{
ThrowIfDisposed();
_subscriptionsManager.Unsubscribe(options);
_workSignal.Set();
return PlatformAbstractionLayer.CompletedTask;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
}
}
Task OnInternalClientDisconnected(MqttClientDisconnectedEventArgs eventArgs)
{
_workSignal.Set();
return PlatformAbstractionLayer.CompletedTask;
}
async Task UpdateConnectionStatus(MaintainConnectionResult maintainConnectionResult)
{
var isConnected = maintainConnectionResult != MaintainConnectionResult.NotConnected;
if (IsConnected.Equals(isConnected))
{
return;
}
IsConnected = isConnected;
if (_isConnectedChangedEvent.HasHandlers)
{
await _isConnectedChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
}
}
async Task WorkerLoop(CancellationToken cancellationToken)
{
try
{
IsStarted = true;
while (!cancellationToken.IsCancellationRequested)
{
_workSignal.Wait(cancellationToken);
_workSignal.Reset();
var maintainConnectionResult = await _connectionManager.MaintainConnection(Options, cancellationToken).ConfigureAwait(false);
await UpdateConnectionStatus(maintainConnectionResult).ConfigureAwait(false);
if (maintainConnectionResult == MaintainConnectionResult.ConnectedEstablished && Options.ClientOptions.CleanSession)
{
// Since the session is clean the server will now know any subscription so that
// we have to subscribe to all of them again.
_subscriptionsManager.ResubscribeAll();
}
if (IsConnected)
{
await _subscriptionsManager.Synchronize(cancellationToken).ConfigureAwait(false);
}
if (IsConnected)
{
// TODO: Move into pending messages manager + locking.
while (_applicationMessagesManager.TryPeek(out var nextPendingMessage))
{
Exception processingException = null;
try
{
_logger.Verbose("Publishing managed application message {0}.", nextPendingMessage.Id);
await InternalClient.PublishAsync(nextPendingMessage.ApplicationMessage, cancellationToken).ConfigureAwait(false);
// The message was delivered successfully. So we can get rid of it.
_applicationMessagesManager.Dequeue();
}
catch (Exception exception)
{
processingException = exception;
}
finally
{
if (_applicationMessageProcessedEvent.HasHandlers)
{
var eventArgs = new ApplicationMessageProcessedEventArgs(nextPendingMessage, processingException);
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
}
}
}
catch (OperationCanceledException)
{
}
#if !NETSTANDARD1_3
catch (ThreadAbortException)
{
}
#endif
catch (Exception exception)
{
_logger.Error(exception, "Error while maintaining connection.");
}
finally
{
IsStarted = false;
await UpdateConnectionStatus(MaintainConnectionResult.NotConnected).ConfigureAwait(false);
}
// TODO: Disconnected!
}
}
}
@@ -0,0 +1,40 @@
// 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 MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClientOptions
{
public ManagedMqttClientOptions()
{
}
public ManagedMqttClientOptions(MqttClientOptions clientOptions)
{
ClientOptions = clientOptions;
}
public MqttClientOptions ClientOptions { get; set; }
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
public IManagedMqttClientStorage Storage { get; set; }
public int MaxPendingMessages { get; set; } = int.MaxValue;
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage;
/// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
/// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
}
}
@@ -0,0 +1,97 @@
// 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 MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClientOptionsBuilder
{
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
MqttClientOptionsBuilder _clientOptionsBuilder;
public ManagedMqttClientOptionsBuilder WithMaxPendingMessages(int value)
{
_options.MaxPendingMessages = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
{
_options.PendingMessagesOverflowStrategy = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
{
_options.AutoReconnectDelay = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithStorage(IManagedMqttClientStorage value)
{
_options.Storage = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptions value)
{
if (_clientOptionsBuilder != null)
{
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
}
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptionsBuilder builder)
{
if (_options.ClientOptions != null)
{
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
}
_clientOptionsBuilder = builder;
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(Action<MqttClientOptionsBuilder> options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (_clientOptionsBuilder == null)
{
_clientOptionsBuilder = new MqttClientOptionsBuilder();
}
options(_clientOptionsBuilder);
return this;
}
public ManagedMqttClientOptionsBuilder WithMaxTopicFiltersInSubscribeUnsubscribePackets(int value)
{
_options.MaxTopicFiltersInSubscribeUnsubscribePackets = value;
return this;
}
public ManagedMqttClientOptions Build()
{
if (_clientOptionsBuilder != null)
{
_options.ClientOptions = _clientOptionsBuilder.Build();
}
if (_options.ClientOptions == null)
{
throw new InvalidOperationException("The ClientOptions cannot be null.");
}
return _options;
}
}
}