You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

763 lines
29 KiB

  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using MQTTnet.Client;
  10. using MQTTnet.Diagnostics;
  11. using MQTTnet.Exceptions;
  12. using MQTTnet.Internal;
  13. using MQTTnet.Packets;
  14. using MQTTnet.Protocol;
  15. using MQTTnet.Server;
  16. using MqttClient = MQTTnet.Client.MqttClient;
  17. namespace MQTTnet.Extensions.ManagedClient
  18. {
  19. public sealed class ManagedMqttClient : Disposable
  20. {
  21. readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
  22. readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
  23. readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
  24. readonly MqttNetSourceLogger _logger;
  25. readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
  26. readonly AsyncLock _messageQueueLock = new AsyncLock();
  27. /// <summary>
  28. /// The subscriptions are managed in 2 separate buckets:
  29. /// <see
  30. /// cref="_subscriptions" />
  31. /// and
  32. /// <see
  33. /// cref="_unsubscriptions" />
  34. /// are processed during normal operation
  35. /// and are moved to the
  36. /// <see
  37. /// cref="_reconnectSubscriptions" />
  38. /// when they get processed. They can be accessed by
  39. /// any thread and are therefore mutex'ed.
  40. /// <see
  41. /// cref="_reconnectSubscriptions" />
  42. /// get sent to the broker
  43. /// at reconnect and are solely owned by
  44. /// <see
  45. /// cref="MaintainConnectionAsync" />
  46. /// .
  47. /// </summary>
  48. readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
  49. readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
  50. readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
  51. readonly HashSet<string> _unsubscriptions = new HashSet<string>();
  52. CancellationTokenSource _connectionCancellationToken;
  53. Task _maintainConnectionTask;
  54. CancellationTokenSource _publishingCancellationToken;
  55. ManagedMqttClientStorageManager _storageManager;
  56. public ManagedMqttClient(MqttClient mqttClient, IMqttNetLogger logger)
  57. {
  58. InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
  59. if (logger == null)
  60. {
  61. throw new ArgumentNullException(nameof(logger));
  62. }
  63. _logger = logger.WithSource(nameof(ManagedMqttClient));
  64. }
  65. public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
  66. {
  67. add => _applicationMessageProcessedEvent.AddHandler(value);
  68. remove => _applicationMessageProcessedEvent.RemoveHandler(value);
  69. }
  70. public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
  71. {
  72. add => InternalClient.ApplicationMessageReceivedAsync += value;
  73. remove => InternalClient.ApplicationMessageReceivedAsync -= value;
  74. }
  75. public event Func<EventArgs, Task> ConnectedAsync
  76. {
  77. add => InternalClient.ConnectedAsync += value;
  78. remove => InternalClient.ConnectedAsync -= value;
  79. }
  80. public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
  81. {
  82. add => _connectingFailedEvent.AddHandler(value);
  83. remove => _connectingFailedEvent.RemoveHandler(value);
  84. }
  85. public event Func<EventArgs, Task> ConnectionStateChangedAsync
  86. {
  87. add => _connectionStateChangedEvent.AddHandler(value);
  88. remove => _connectionStateChangedEvent.RemoveHandler(value);
  89. }
  90. public event Func<EventArgs, Task> DisconnectedAsync
  91. {
  92. add => InternalClient.DisconnectedAsync += value;
  93. remove => InternalClient.DisconnectedAsync -= value;
  94. }
  95. public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
  96. public MqttClient InternalClient { get; }
  97. public bool IsConnected => InternalClient.IsConnected;
  98. public bool IsStarted => _connectionCancellationToken != null;
  99. public ManagedMqttClientOptions Options { get; private set; }
  100. public int PendingApplicationMessagesCount => _messageQueue.Count;
  101. public ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
  102. public async Task EnqueueAsync(MqttApplicationMessage applicationMessage)
  103. {
  104. ThrowIfDisposed();
  105. if (applicationMessage == null)
  106. {
  107. throw new ArgumentNullException(nameof(applicationMessage));
  108. }
  109. var managedMqttApplicationMessage = new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage);
  110. await EnqueueAsync(managedMqttApplicationMessage.Build()).ConfigureAwait(false);
  111. }
  112. public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
  113. {
  114. ThrowIfDisposed();
  115. if (applicationMessage == null)
  116. {
  117. throw new ArgumentNullException(nameof(applicationMessage));
  118. }
  119. if (Options == null)
  120. {
  121. throw new InvalidOperationException("call StartAsync before publishing messages");
  122. }
  123. MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
  124. ManagedMqttApplicationMessage removedMessage = null;
  125. ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
  126. try
  127. {
  128. using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
  129. {
  130. if (_messageQueue.Count >= Options.MaxPendingMessages)
  131. {
  132. if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
  133. {
  134. _logger.Verbose("Skipping publish of new application message because internal queue is full.");
  135. applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
  136. return;
  137. }
  138. if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
  139. {
  140. removedMessage = _messageQueue.RemoveFirst();
  141. _logger.Verbose("Removed oldest application message from internal queue because it is full.");
  142. applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
  143. }
  144. }
  145. _messageQueue.Enqueue(applicationMessage);
  146. if (_storageManager != null)
  147. {
  148. if (removedMessage != null)
  149. {
  150. await _storageManager.RemoveAsync(removedMessage).ConfigureAwait(false);
  151. }
  152. await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
  153. }
  154. }
  155. }
  156. finally
  157. {
  158. if (applicationMessageSkippedEventArgs != null)
  159. {
  160. var applicationMessageSkippedHandler = ApplicationMessageSkippedHandler;
  161. if (applicationMessageSkippedHandler != null)
  162. {
  163. await applicationMessageSkippedHandler.HandleApplicationMessageSkippedAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
  164. }
  165. }
  166. }
  167. }
  168. public Task PingAsync(CancellationToken cancellationToken)
  169. {
  170. return InternalClient.PingAsync(cancellationToken);
  171. }
  172. public async Task StartAsync(ManagedMqttClientOptions options)
  173. {
  174. ThrowIfDisposed();
  175. if (options == null)
  176. {
  177. throw new ArgumentNullException(nameof(options));
  178. }
  179. if (options.ClientOptions == null)
  180. {
  181. throw new ArgumentException("The client options are not set.", nameof(options));
  182. }
  183. if (!_maintainConnectionTask?.IsCompleted ?? false)
  184. {
  185. throw new InvalidOperationException("The managed client is already started.");
  186. }
  187. Options = options;
  188. if (options.Storage != null)
  189. {
  190. _storageManager = new ManagedMqttClientStorageManager(options.Storage);
  191. var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
  192. foreach (var message in messages)
  193. {
  194. _messageQueue.Enqueue(message);
  195. }
  196. }
  197. var cancellationTokenSource = new CancellationTokenSource();
  198. var cancellationToken = cancellationTokenSource.Token;
  199. _connectionCancellationToken = cancellationTokenSource;
  200. _maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(cancellationToken), cancellationToken);
  201. _maintainConnectionTask.RunInBackground(_logger);
  202. _logger.Info("Started");
  203. }
  204. public async Task StopAsync()
  205. {
  206. ThrowIfDisposed();
  207. StopPublishing();
  208. StopMaintainingConnection();
  209. _messageQueue.Clear();
  210. if (_maintainConnectionTask != null)
  211. {
  212. await Task.WhenAny(_maintainConnectionTask);
  213. _maintainConnectionTask = null;
  214. }
  215. }
  216. public Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters)
  217. {
  218. ThrowIfDisposed();
  219. if (topicFilters == null)
  220. {
  221. throw new ArgumentNullException(nameof(topicFilters));
  222. }
  223. foreach (var topicFilter in topicFilters)
  224. {
  225. MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
  226. }
  227. lock (_subscriptions)
  228. {
  229. foreach (var topicFilter in topicFilters)
  230. {
  231. _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
  232. _unsubscriptions.Remove(topicFilter.Topic);
  233. }
  234. }
  235. _subscriptionsQueuedSignal.Release();
  236. return Task.FromResult(0);
  237. }
  238. public Task UnsubscribeAsync(ICollection<string> topics)
  239. {
  240. ThrowIfDisposed();
  241. if (topics == null)
  242. {
  243. throw new ArgumentNullException(nameof(topics));
  244. }
  245. lock (_subscriptions)
  246. {
  247. foreach (var topic in topics)
  248. {
  249. _subscriptions.Remove(topic);
  250. _unsubscriptions.Add(topic);
  251. }
  252. }
  253. _subscriptionsQueuedSignal.Release();
  254. return Task.FromResult(0);
  255. }
  256. protected override void Dispose(bool disposing)
  257. {
  258. if (disposing)
  259. {
  260. StopPublishing();
  261. StopMaintainingConnection();
  262. if (_maintainConnectionTask != null)
  263. {
  264. _maintainConnectionTask.GetAwaiter().GetResult();
  265. _maintainConnectionTask = null;
  266. }
  267. _messageQueue.Dispose();
  268. _messageQueueLock.Dispose();
  269. InternalClient.Dispose();
  270. _subscriptionsQueuedSignal.Dispose();
  271. }
  272. base.Dispose(disposing);
  273. }
  274. static TimeSpan GetRemainingTime(DateTime endTime)
  275. {
  276. var remainingTime = endTime - DateTime.UtcNow;
  277. return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
  278. }
  279. async Task HandleSubscriptionExceptionAsync(Exception exception)
  280. {
  281. _logger.Warning(exception, "Synchronizing subscriptions failed.");
  282. var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler;
  283. if (SynchronizingSubscriptionsFailedHandler != null)
  284. {
  285. await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
  286. }
  287. }
  288. async Task MaintainConnectionAsync(CancellationToken cancellationToken)
  289. {
  290. try
  291. {
  292. while (!cancellationToken.IsCancellationRequested)
  293. {
  294. await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
  295. }
  296. }
  297. catch (OperationCanceledException)
  298. {
  299. }
  300. catch (Exception exception)
  301. {
  302. _logger.Error(exception, "Error exception while maintaining connection.");
  303. }
  304. finally
  305. {
  306. if (!IsDisposed)
  307. {
  308. try
  309. {
  310. using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
  311. {
  312. await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
  313. }
  314. }
  315. catch (OperationCanceledException)
  316. {
  317. _logger.Warning("Timeout while sending DISCONNECT packet.");
  318. }
  319. catch (Exception exception)
  320. {
  321. _logger.Error(exception, "Error while disconnecting.");
  322. }
  323. _logger.Info("Stopped");
  324. }
  325. _reconnectSubscriptions.Clear();
  326. lock (_subscriptions)
  327. {
  328. _subscriptions.Clear();
  329. _unsubscriptions.Clear();
  330. }
  331. }
  332. }
  333. async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
  334. {
  335. try
  336. {
  337. while (!cancellationToken.IsCancellationRequested && InternalClient.IsConnected)
  338. {
  339. // Peek at the message without dequeueing in order to prevent the
  340. // possibility of the queue growing beyond the configured cap.
  341. // Previously, messages could be re-enqueued if there was an
  342. // exception, and this re-enqueueing did not honor the cap.
  343. // Furthermore, because re-enqueueing would shuffle the order
  344. // of the messages, the DropOldestQueuedMessage strategy would
  345. // be unable to know which message is actually the oldest and would
  346. // instead drop the first item in the queue.
  347. var message = _messageQueue.PeekAndWait(cancellationToken);
  348. if (message == null)
  349. {
  350. continue;
  351. }
  352. cancellationToken.ThrowIfCancellationRequested();
  353. await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
  354. }
  355. }
  356. catch (OperationCanceledException)
  357. {
  358. }
  359. catch (Exception exception)
  360. {
  361. _logger.Error(exception, "Error while publishing queued application messages.");
  362. }
  363. finally
  364. {
  365. _logger.Verbose("Stopped publishing messages.");
  366. }
  367. }
  368. async Task PublishReconnectSubscriptionsAsync()
  369. {
  370. _logger.Info("Publishing subscriptions at reconnect");
  371. try
  372. {
  373. if (_reconnectSubscriptions.Any())
  374. {
  375. var subscriptions = _reconnectSubscriptions.Select(
  376. i => new MqttTopicFilter
  377. {
  378. Topic = i.Key,
  379. QualityOfServiceLevel = i.Value
  380. });
  381. var topicFilters = new List<MqttTopicFilter>();
  382. foreach (var sub in subscriptions)
  383. {
  384. topicFilters.Add(sub);
  385. if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
  386. {
  387. await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
  388. topicFilters.Clear();
  389. }
  390. }
  391. await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
  392. }
  393. }
  394. catch (Exception exception)
  395. {
  396. await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
  397. }
  398. }
  399. async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
  400. {
  401. var endTime = DateTime.UtcNow + timeout;
  402. while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
  403. {
  404. List<MqttTopicFilter> subscriptions;
  405. HashSet<string> unsubscriptions;
  406. lock (_subscriptions)
  407. {
  408. subscriptions = _subscriptions.Select(
  409. i => new MqttTopicFilter
  410. {
  411. Topic = i.Key,
  412. QualityOfServiceLevel = i.Value
  413. })
  414. .ToList();
  415. _subscriptions.Clear();
  416. unsubscriptions = new HashSet<string>(_unsubscriptions);
  417. _unsubscriptions.Clear();
  418. }
  419. if (!subscriptions.Any() && !unsubscriptions.Any())
  420. {
  421. continue;
  422. }
  423. _logger.Verbose("Publishing {0} added and {1} removed subscriptions", subscriptions.Count, unsubscriptions.Count);
  424. foreach (var unsubscription in unsubscriptions)
  425. {
  426. _reconnectSubscriptions.Remove(unsubscription);
  427. }
  428. foreach (var subscription in subscriptions)
  429. {
  430. _reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
  431. }
  432. var addedTopicFilters = new List<MqttTopicFilter>();
  433. foreach (var subscription in subscriptions)
  434. {
  435. addedTopicFilters.Add(subscription);
  436. if (addedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
  437. {
  438. await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
  439. addedTopicFilters.Clear();
  440. }
  441. }
  442. await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
  443. var removedTopicFilters = new List<string>();
  444. foreach (var unSub in unsubscriptions)
  445. {
  446. removedTopicFilters.Add(unSub);
  447. if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
  448. {
  449. await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
  450. removedTopicFilters.Clear();
  451. }
  452. }
  453. await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
  454. }
  455. }
  456. async Task<ReconnectionResult> ReconnectIfRequiredAsync(CancellationToken cancellationToken)
  457. {
  458. if (InternalClient.IsConnected)
  459. {
  460. return ReconnectionResult.StillConnected;
  461. }
  462. MqttClientConnectResult connectResult = null;
  463. try
  464. {
  465. using (var connectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
  466. {
  467. connectResult = await InternalClient.ConnectAsync(Options.ClientOptions, connectTimeout.Token).ConfigureAwait(false);
  468. }
  469. if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
  470. {
  471. throw new MqttCommunicationException($"Client connected but server denied connection with reason '{connectResult.ResultCode}'.");
  472. }
  473. return connectResult.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected;
  474. }
  475. catch (Exception exception)
  476. {
  477. await _connectingFailedEvent.InvokeAsync(new ConnectingFailedEventArgs(connectResult, exception));
  478. return ReconnectionResult.NotConnected;
  479. }
  480. }
  481. async Task SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
  482. {
  483. try
  484. {
  485. if (removedSubscriptions != null && removedSubscriptions.Any())
  486. {
  487. var unsubscribeOptionsBuilder = new MqttClientUnsubscribeOptionsBuilder();
  488. foreach (var removedSubscription in removedSubscriptions)
  489. {
  490. unsubscribeOptionsBuilder.WithTopicFilter(removedSubscription);
  491. }
  492. await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
  493. }
  494. if (addedSubscriptions != null && addedSubscriptions.Any())
  495. {
  496. var subscribeOptionsBuilder = new MqttClientSubscribeOptionsBuilder();
  497. foreach (var addedSubscription in addedSubscriptions)
  498. {
  499. subscribeOptionsBuilder.WithTopicFilter(addedSubscription);
  500. }
  501. await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build()).ConfigureAwait(false);
  502. }
  503. }
  504. catch (Exception exception)
  505. {
  506. await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
  507. }
  508. }
  509. void StartPublishing()
  510. {
  511. StopPublishing();
  512. var cancellationTokenSource = new CancellationTokenSource();
  513. var cancellationToken = cancellationTokenSource.Token;
  514. _publishingCancellationToken = cancellationTokenSource;
  515. Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
  516. }
  517. void StopMaintainingConnection()
  518. {
  519. try
  520. {
  521. _connectionCancellationToken?.Cancel(false);
  522. }
  523. finally
  524. {
  525. _connectionCancellationToken?.Dispose();
  526. _connectionCancellationToken = null;
  527. }
  528. }
  529. void StopPublishing()
  530. {
  531. try
  532. {
  533. _publishingCancellationToken?.Cancel(false);
  534. }
  535. finally
  536. {
  537. _publishingCancellationToken?.Dispose();
  538. _publishingCancellationToken = null;
  539. }
  540. }
  541. async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
  542. {
  543. try
  544. {
  545. var oldConnectionState = InternalClient.IsConnected;
  546. var connectionState = await ReconnectIfRequiredAsync(cancellationToken).ConfigureAwait(false);
  547. if (connectionState == ReconnectionResult.NotConnected)
  548. {
  549. StopPublishing();
  550. await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
  551. }
  552. else if (connectionState == ReconnectionResult.Reconnected)
  553. {
  554. await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
  555. StartPublishing();
  556. }
  557. else if (connectionState == ReconnectionResult.Recovered)
  558. {
  559. StartPublishing();
  560. }
  561. else if (connectionState == ReconnectionResult.StillConnected)
  562. {
  563. await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
  564. }
  565. if (oldConnectionState != InternalClient.IsConnected)
  566. {
  567. await _connectionStateChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
  568. }
  569. }
  570. catch (OperationCanceledException)
  571. {
  572. }
  573. catch (MqttCommunicationException exception)
  574. {
  575. _logger.Warning(exception, "Communication error while maintaining connection.");
  576. }
  577. catch (Exception exception)
  578. {
  579. _logger.Error(exception, "Error exception while maintaining connection.");
  580. }
  581. }
  582. async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
  583. {
  584. Exception transmitException = null;
  585. try
  586. {
  587. await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
  588. using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
  589. {
  590. // While publishing this message, this.PublishAsync could have booted this
  591. // message off the queue to make room for another (when using a cap
  592. // with the DropOldestQueuedMessage strategy). If the first item
  593. // in the queue is equal to this message, then it's safe to remove
  594. // it from the queue. If not, that means this.PublishAsync has already
  595. // removed it, in which case we don't want to do anything.
  596. _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
  597. if (_storageManager != null)
  598. {
  599. await _storageManager.RemoveAsync(message).ConfigureAwait(false);
  600. }
  601. }
  602. }
  603. catch (MqttCommunicationException exception)
  604. {
  605. transmitException = exception;
  606. _logger.Warning(exception, "Publishing application message ({0}) failed.", message.Id);
  607. if (message.ApplicationMessage.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
  608. {
  609. //If QoS 0, we don't want this message to stay on the queue.
  610. //If QoS 1 or 2, it's possible that, when using a cap, this message
  611. //has been booted off the queue by this.PublishAsync, in which case this
  612. //thread will not continue to try to publish it. While this does
  613. //contradict the expected behavior of QoS 1 and 2, that's also true
  614. //for the usage of a message queue cap, so it's still consistent
  615. //with prior behavior in that way.
  616. using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
  617. {
  618. _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
  619. if (_storageManager != null)
  620. {
  621. await _storageManager.RemoveAsync(message).ConfigureAwait(false);
  622. }
  623. }
  624. }
  625. }
  626. catch (Exception exception)
  627. {
  628. transmitException = exception;
  629. _logger.Error(exception, "Error while publishing application message ({0}).", message.Id);
  630. }
  631. finally
  632. {
  633. if (_applicationMessageProcessedEvent.HasHandlers)
  634. {
  635. var eventArgs = new ApplicationMessageProcessedEventArgs(message, transmitException);
  636. await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
  637. }
  638. }
  639. }
  640. }
  641. }