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.

ManagedMqttClient.cs 23 KiB

Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
7 years ago
6 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MQTTnet.Client;
  7. using MQTTnet.Client.Connecting;
  8. using MQTTnet.Client.Disconnecting;
  9. using MQTTnet.Client.Publishing;
  10. using MQTTnet.Client.Receiving;
  11. using MQTTnet.Diagnostics;
  12. using MQTTnet.Exceptions;
  13. using MQTTnet.Internal;
  14. using MQTTnet.Protocol;
  15. using MQTTnet.Server;
  16. namespace MQTTnet.Extensions.ManagedClient
  17. {
  18. public class ManagedMqttClient : Disposable, IManagedMqttClient
  19. {
  20. private readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
  21. /// <summary>
  22. /// The subscriptions are managed in 2 separate buckets:
  23. /// <see cref="_subscriptions"/> and <see cref="_unsubscriptions"/> are processed during normal operation
  24. /// and are moved to the <see cref="_reconnectSubscriptions"/> when they get processed. They can be accessed by
  25. /// any thread and are therefore mutex'ed. <see cref="_reconnectSubscriptions"/> get sent to the broker
  26. /// at reconnect and are solely owned by <see cref="MaintainConnectionAsync"/>.
  27. /// </summary>
  28. private readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
  29. private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
  30. private readonly HashSet<string> _unsubscriptions = new HashSet<string>();
  31. private readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
  32. private readonly IMqttClient _mqttClient;
  33. private readonly IMqttNetChildLogger _logger;
  34. private readonly AsyncLock _messageQueueLock = new AsyncLock();
  35. private CancellationTokenSource _connectionCancellationToken;
  36. private CancellationTokenSource _publishingCancellationToken;
  37. private Task _maintainConnectionTask;
  38. private ManagedMqttClientStorageManager _storageManager;
  39. public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger)
  40. {
  41. _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
  42. if (logger == null) throw new ArgumentNullException(nameof(logger));
  43. _logger = logger.CreateChildLogger(nameof(ManagedMqttClient));
  44. }
  45. public bool IsConnected => _mqttClient.IsConnected;
  46. public bool IsStarted => _connectionCancellationToken != null;
  47. public int PendingApplicationMessagesCount => _messageQueue.Count;
  48. public IManagedMqttClientOptions Options { get; private set; }
  49. public IMqttClientConnectedHandler ConnectedHandler
  50. {
  51. get => _mqttClient.ConnectedHandler;
  52. set => _mqttClient.ConnectedHandler = value;
  53. }
  54. public IMqttClientDisconnectedHandler DisconnectedHandler
  55. {
  56. get => _mqttClient.DisconnectedHandler;
  57. set => _mqttClient.DisconnectedHandler = value;
  58. }
  59. public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler
  60. {
  61. get => _mqttClient.ApplicationMessageReceivedHandler;
  62. set => _mqttClient.ApplicationMessageReceivedHandler = value;
  63. }
  64. public IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; }
  65. public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
  66. public IConnectingFailedHandler ConnectingFailedHandler { get; set; }
  67. public ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
  68. public async Task StartAsync(IManagedMqttClientOptions options)
  69. {
  70. ThrowIfDisposed();
  71. if (options == null) throw new ArgumentNullException(nameof(options));
  72. if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
  73. if (!_maintainConnectionTask?.IsCompleted ?? false) throw new InvalidOperationException("The managed client is already started.");
  74. Options = options;
  75. if (Options.Storage != null)
  76. {
  77. _storageManager = new ManagedMqttClientStorageManager(Options.Storage);
  78. var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
  79. foreach (var message in messages)
  80. {
  81. _messageQueue.Enqueue(message);
  82. }
  83. }
  84. _connectionCancellationToken = new CancellationTokenSource();
  85. _maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
  86. _maintainConnectionTask.Forget(_logger);
  87. _logger.Info("Started");
  88. }
  89. public async Task StopAsync()
  90. {
  91. ThrowIfDisposed();
  92. StopPublishing();
  93. StopMaintainingConnection();
  94. _messageQueue.Clear();
  95. if (_maintainConnectionTask != null)
  96. {
  97. await Task.WhenAny(_maintainConnectionTask);
  98. _maintainConnectionTask = null;
  99. }
  100. }
  101. public async Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
  102. {
  103. ThrowIfDisposed();
  104. if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
  105. await PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build()).ConfigureAwait(false);
  106. return new MqttClientPublishResult();
  107. }
  108. public async Task PublishAsync(ManagedMqttApplicationMessage applicationMessage)
  109. {
  110. ThrowIfDisposed();
  111. if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
  112. if (Options == null) throw new InvalidOperationException("call StartAsync before publishing messages");
  113. MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
  114. ManagedMqttApplicationMessage removedMessage = null;
  115. ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
  116. try
  117. {
  118. using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
  119. {
  120. if (_messageQueue.Count >= Options.MaxPendingMessages)
  121. {
  122. if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
  123. {
  124. _logger.Verbose("Skipping publish of new application message because internal queue is full.");
  125. applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
  126. return;
  127. }
  128. if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
  129. {
  130. removedMessage = _messageQueue.RemoveFirst();
  131. _logger.Verbose("Removed oldest application message from internal queue because it is full.");
  132. applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
  133. }
  134. }
  135. _messageQueue.Enqueue(applicationMessage);
  136. if (_storageManager != null)
  137. {
  138. if (removedMessage != null)
  139. {
  140. await _storageManager.RemoveAsync(removedMessage).ConfigureAwait(false);
  141. }
  142. await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
  143. }
  144. }
  145. }
  146. finally
  147. {
  148. if (applicationMessageSkippedEventArgs != null)
  149. {
  150. var applicationMessageSkippedHandler = ApplicationMessageSkippedHandler;
  151. if (applicationMessageSkippedHandler != null)
  152. {
  153. await applicationMessageSkippedHandler.HandleApplicationMessageSkippedAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
  154. }
  155. }
  156. }
  157. }
  158. public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
  159. {
  160. ThrowIfDisposed();
  161. if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
  162. lock (_subscriptions)
  163. {
  164. foreach (var topicFilter in topicFilters)
  165. {
  166. _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
  167. _unsubscriptions.Remove(topicFilter.Topic);
  168. }
  169. }
  170. _subscriptionsQueuedSignal.Release();
  171. return Task.FromResult(0);
  172. }
  173. public Task UnsubscribeAsync(IEnumerable<string> topics)
  174. {
  175. ThrowIfDisposed();
  176. if (topics == null) throw new ArgumentNullException(nameof(topics));
  177. lock (_subscriptions)
  178. {
  179. foreach (var topic in topics)
  180. {
  181. _subscriptions.Remove(topic);
  182. _unsubscriptions.Add(topic);
  183. }
  184. }
  185. _subscriptionsQueuedSignal.Release();
  186. return Task.FromResult(0);
  187. }
  188. protected override void Dispose(bool disposing)
  189. {
  190. if (disposing)
  191. {
  192. StopPublishing();
  193. StopMaintainingConnection();
  194. if (_maintainConnectionTask != null)
  195. {
  196. _maintainConnectionTask.GetAwaiter().GetResult();
  197. _maintainConnectionTask = null;
  198. }
  199. _messageQueue.Dispose();
  200. _messageQueueLock.Dispose();
  201. _mqttClient.Dispose();
  202. _subscriptionsQueuedSignal.Dispose();
  203. }
  204. base.Dispose(disposing);
  205. }
  206. private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
  207. {
  208. try
  209. {
  210. while (!cancellationToken.IsCancellationRequested)
  211. {
  212. await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
  213. }
  214. }
  215. catch (OperationCanceledException)
  216. {
  217. }
  218. catch (Exception exception)
  219. {
  220. _logger.Error(exception, "Error exception while maintaining connection.");
  221. }
  222. finally
  223. {
  224. if (!IsDisposed)
  225. {
  226. try
  227. {
  228. await _mqttClient.DisconnectAsync().ConfigureAwait(false);
  229. }
  230. catch (Exception exception)
  231. {
  232. _logger.Error(exception, "Error while disconnecting.");
  233. }
  234. _logger.Info("Stopped");
  235. }
  236. _reconnectSubscriptions.Clear();
  237. lock (_subscriptions)
  238. {
  239. _subscriptions.Clear();
  240. _unsubscriptions.Clear();
  241. }
  242. }
  243. }
  244. private async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
  245. {
  246. try
  247. {
  248. var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false);
  249. if (connectionState == ReconnectionResult.NotConnected)
  250. {
  251. StopPublishing();
  252. await Task.Delay(Options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
  253. return;
  254. }
  255. if (connectionState == ReconnectionResult.Reconnected)
  256. {
  257. await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
  258. StartPublishing();
  259. return;
  260. }
  261. if (connectionState == ReconnectionResult.Recovered)
  262. {
  263. StartPublishing();
  264. return;
  265. }
  266. if (connectionState == ReconnectionResult.StillConnected)
  267. {
  268. await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
  269. }
  270. }
  271. catch (OperationCanceledException)
  272. {
  273. }
  274. catch (MqttCommunicationException exception)
  275. {
  276. _logger.Warning(exception, "Communication error while maintaining connection.");
  277. }
  278. catch (Exception exception)
  279. {
  280. _logger.Error(exception, "Error exception while maintaining connection.");
  281. }
  282. }
  283. private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
  284. {
  285. try
  286. {
  287. while (!cancellationToken.IsCancellationRequested && _mqttClient.IsConnected)
  288. {
  289. // Peek at the message without dequeueing in order to prevent the
  290. // possibility of the queue growing beyond the configured cap.
  291. // Previously, messages could be re-enqueued if there was an
  292. // exception, and this re-enqueueing did not honor the cap.
  293. // Furthermore, because re-enqueueing would shuffle the order
  294. // of the messages, the DropOldestQueuedMessage strategy would
  295. // be unable to know which message is actually the oldest and would
  296. // instead drop the first item in the queue.
  297. var message = _messageQueue.PeekAndWait(cancellationToken);
  298. if (message == null)
  299. {
  300. continue;
  301. }
  302. cancellationToken.ThrowIfCancellationRequested();
  303. await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
  304. }
  305. }
  306. catch (OperationCanceledException)
  307. {
  308. }
  309. catch (Exception exception)
  310. {
  311. _logger.Error(exception, "Error while publishing queued application messages.");
  312. }
  313. finally
  314. {
  315. _logger.Verbose("Stopped publishing messages.");
  316. }
  317. }
  318. private async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
  319. {
  320. Exception transmitException = null;
  321. try
  322. {
  323. await _mqttClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
  324. using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
  325. {
  326. // While publishing this message, this.PublishAsync could have booted this
  327. // message off the queue to make room for another (when using a cap
  328. // with the DropOldestQueuedMessage strategy). If the first item
  329. // in the queue is equal to this message, then it's safe to remove
  330. // it from the queue. If not, that means this.PublishAsync has already
  331. // removed it, in which case we don't want to do anything.
  332. _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
  333. if (_storageManager != null)
  334. {
  335. await _storageManager.RemoveAsync(message).ConfigureAwait(false);
  336. }
  337. }
  338. }
  339. catch (MqttCommunicationException exception)
  340. {
  341. transmitException = exception;
  342. _logger.Warning(exception, $"Publishing application ({message.Id}) message failed.");
  343. if (message.ApplicationMessage.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
  344. {
  345. //If QoS 0, we don't want this message to stay on the queue.
  346. //If QoS 1 or 2, it's possible that, when using a cap, this message
  347. //has been booted off the queue by this.PublishAsync, in which case this
  348. //thread will not continue to try to publish it. While this does
  349. //contradict the expected behavior of QoS 1 and 2, that's also true
  350. //for the usage of a message queue cap, so it's still consistent
  351. //with prior behavior in that way.
  352. using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
  353. {
  354. _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
  355. if (_storageManager != null)
  356. {
  357. await _storageManager.RemoveAsync(message).ConfigureAwait(false);
  358. }
  359. }
  360. }
  361. }
  362. catch (Exception exception)
  363. {
  364. transmitException = exception;
  365. _logger.Error(exception, $"Error while publishing application message ({message.Id}).");
  366. }
  367. finally
  368. {
  369. var eventHandler = ApplicationMessageProcessedHandler;
  370. if (eventHandler != null)
  371. {
  372. var eventArguments = new ApplicationMessageProcessedEventArgs(message, transmitException);
  373. await eventHandler.HandleApplicationMessageProcessedAsync(eventArguments).ConfigureAwait(false);
  374. }
  375. }
  376. }
  377. private async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
  378. {
  379. var endTime = DateTime.UtcNow + timeout;
  380. while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
  381. {
  382. List<TopicFilter> subscriptions;
  383. HashSet<string> unsubscriptions;
  384. lock (_subscriptions)
  385. {
  386. subscriptions = _subscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value }).ToList();
  387. _subscriptions.Clear();
  388. unsubscriptions = new HashSet<string>(_unsubscriptions);
  389. _unsubscriptions.Clear();
  390. }
  391. if (!subscriptions.Any() && !unsubscriptions.Any())
  392. {
  393. continue;
  394. }
  395. _logger.Verbose($"Publishing subscriptions ({subscriptions.Count} subscriptions and {unsubscriptions.Count} unsubscriptions)");
  396. foreach (var unsubscription in unsubscriptions)
  397. {
  398. _reconnectSubscriptions.Remove(unsubscription);
  399. }
  400. foreach (var subscription in subscriptions)
  401. {
  402. _reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
  403. }
  404. try
  405. {
  406. if (unsubscriptions.Any())
  407. {
  408. await _mqttClient.UnsubscribeAsync(unsubscriptions.ToArray()).ConfigureAwait(false);
  409. }
  410. if (subscriptions.Any())
  411. {
  412. await _mqttClient.SubscribeAsync(subscriptions.ToArray()).ConfigureAwait(false);
  413. }
  414. }
  415. catch (Exception exception)
  416. {
  417. await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
  418. }
  419. }
  420. }
  421. private async Task PublishReconnectSubscriptionsAsync()
  422. {
  423. _logger.Info("Publishing subscriptions at reconnect");
  424. try
  425. {
  426. if (_reconnectSubscriptions.Any())
  427. {
  428. var subscriptions = _reconnectSubscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value });
  429. await _mqttClient.SubscribeAsync(subscriptions.ToArray()).ConfigureAwait(false);
  430. }
  431. }
  432. catch (Exception exception)
  433. {
  434. await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
  435. }
  436. }
  437. private async Task HandleSubscriptionExceptionAsync(Exception exception)
  438. {
  439. _logger.Warning(exception, "Synchronizing subscriptions failed.");
  440. var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler;
  441. if (SynchronizingSubscriptionsFailedHandler != null)
  442. {
  443. await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
  444. }
  445. }
  446. private async Task<ReconnectionResult> ReconnectIfRequiredAsync()
  447. {
  448. if (_mqttClient.IsConnected)
  449. {
  450. return ReconnectionResult.StillConnected;
  451. }
  452. try
  453. {
  454. var result = await _mqttClient.ConnectAsync(Options.ClientOptions).ConfigureAwait(false);
  455. return result.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected;
  456. }
  457. catch (Exception exception)
  458. {
  459. var connectingFailedHandler = ConnectingFailedHandler;
  460. if (connectingFailedHandler != null)
  461. {
  462. await connectingFailedHandler.HandleConnectingFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
  463. }
  464. return ReconnectionResult.NotConnected;
  465. }
  466. }
  467. private void StartPublishing()
  468. {
  469. if (_publishingCancellationToken != null)
  470. {
  471. StopPublishing();
  472. }
  473. var cts = new CancellationTokenSource();
  474. _publishingCancellationToken = cts;
  475. Task.Run(() => PublishQueuedMessagesAsync(cts.Token), cts.Token).Forget(_logger);
  476. }
  477. private void StopPublishing()
  478. {
  479. _publishingCancellationToken?.Cancel(false);
  480. _publishingCancellationToken?.Dispose();
  481. _publishingCancellationToken = null;
  482. }
  483. private void StopMaintainingConnection()
  484. {
  485. _connectionCancellationToken?.Cancel(false);
  486. _connectionCancellationToken?.Dispose();
  487. _connectionCancellationToken = null;
  488. }
  489. private TimeSpan GetRemainingTime(DateTime endTime)
  490. {
  491. var remainingTime = endTime - DateTime.UtcNow;
  492. return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
  493. }
  494. }
  495. }