Commit 81f19eca4dcadb52f6d261f62dbb87c5691e8a9c
1 parent
d502382e
fix(connection): implement configurable connection timeout/backoff
Showing
5 changed files
with
73 additions
and
25 deletions
include/TrueMQTT.h
| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | |
| 8 | 8 | #pragma once |
| 9 | 9 | |
| 10 | +#include <chrono> | |
| 10 | 11 | #include <functional> |
| 11 | 12 | #include <memory> |
| 12 | 13 | |
| ... | ... | @@ -111,10 +112,17 @@ namespace TrueMQTT |
| 111 | 112 | * @param port Port of the MQTT broker. |
| 112 | 113 | * @param client_id Client ID to use when connecting to the broker. |
| 113 | 114 | * @param connection_timeout Timeout in seconds for the connection to the broker. |
| 115 | + * @param connection_backoff Backoff time when connection to the broker failed. This is doubled every time a connection fails, up till \ref connection_backoff_max. | |
| 114 | 116 | * @param connection_backoff_max Maximum time between backoff attempts in seconds. |
| 115 | 117 | * @param keep_alive_interval Interval in seconds between keep-alive messages. |
| 116 | 118 | */ |
| 117 | - Client(const std::string &host, int port, const std::string &client_id, int connection_timeout = 5, int connection_backoff_max = 30, int keep_alive_interval = 30); | |
| 119 | + Client(const std::string &host, | |
| 120 | + int port, | |
| 121 | + const std::string &client_id, | |
| 122 | + std::chrono::milliseconds connection_timeout = std::chrono::milliseconds(5000), | |
| 123 | + std::chrono::milliseconds connection_backoff = std::chrono::milliseconds(1000), | |
| 124 | + std::chrono::milliseconds connection_backoff_max = std::chrono::milliseconds(30000), | |
| 125 | + std::chrono::milliseconds keep_alive_interval = std::chrono::milliseconds(30000)); | |
| 118 | 126 | |
| 119 | 127 | /** |
| 120 | 128 | * @brief Destructor of the MQTT client. | ... | ... |
src/Client.cpp
| ... | ... | @@ -13,9 +13,15 @@ |
| 13 | 13 | |
| 14 | 14 | #include <sstream> |
| 15 | 15 | |
| 16 | -TrueMQTT::Client::Client(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval) | |
| 16 | +TrueMQTT::Client::Client(const std::string &host, | |
| 17 | + int port, | |
| 18 | + const std::string &client_id, | |
| 19 | + std::chrono::milliseconds connection_timeout, | |
| 20 | + std::chrono::milliseconds connection_backoff, | |
| 21 | + std::chrono::milliseconds connection_backoff_max, | |
| 22 | + std::chrono::milliseconds keep_alive_interval) | |
| 17 | 23 | { |
| 18 | - this->m_impl = std::make_unique<Client::Impl>(host, port, client_id, connection_timeout, connection_backoff_max, keep_alive_interval); | |
| 24 | + this->m_impl = std::make_unique<Client::Impl>(host, port, client_id, connection_timeout, connection_backoff, connection_backoff_max, keep_alive_interval); | |
| 19 | 25 | |
| 20 | 26 | LOG_TRACE(this->m_impl, "Constructor of client called"); |
| 21 | 27 | } |
| ... | ... | @@ -27,11 +33,18 @@ TrueMQTT::Client::~Client() |
| 27 | 33 | this->disconnect(); |
| 28 | 34 | } |
| 29 | 35 | |
| 30 | -TrueMQTT::Client::Impl::Impl(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval) | |
| 36 | +TrueMQTT::Client::Impl::Impl(const std::string &host, | |
| 37 | + int port, | |
| 38 | + const std::string &client_id, | |
| 39 | + std::chrono::milliseconds connection_timeout, | |
| 40 | + std::chrono::milliseconds connection_backoff, | |
| 41 | + std::chrono::milliseconds connection_backoff_max, | |
| 42 | + std::chrono::milliseconds keep_alive_interval) | |
| 31 | 43 | : host(host), |
| 32 | 44 | port(port), |
| 33 | 45 | client_id(client_id), |
| 34 | 46 | connection_timeout(connection_timeout), |
| 47 | + connection_backoff(connection_backoff), | |
| 35 | 48 | connection_backoff_max(connection_backoff_max), |
| 36 | 49 | keep_alive_interval(keep_alive_interval) |
| 37 | 50 | { | ... | ... |
src/ClientImpl.h
| ... | ... | @@ -9,6 +9,7 @@ |
| 9 | 9 | |
| 10 | 10 | #include "TrueMQTT.h" |
| 11 | 11 | |
| 12 | +#include <chrono> | |
| 12 | 13 | #include <deque> |
| 13 | 14 | #include <map> |
| 14 | 15 | #include <mutex> |
| ... | ... | @@ -21,7 +22,13 @@ |
| 21 | 22 | class TrueMQTT::Client::Impl |
| 22 | 23 | { |
| 23 | 24 | public: |
| 24 | - Impl(const std::string &host, int port, const std::string &client_id, int connection_timeout, int connection_backoff_max, int keep_alive_interval); | |
| 25 | + Impl(const std::string &host, | |
| 26 | + int port, | |
| 27 | + const std::string &client_id, | |
| 28 | + std::chrono::milliseconds connection_timeout, | |
| 29 | + std::chrono::milliseconds connection_backoff, | |
| 30 | + std::chrono::milliseconds connection_backoff_max, | |
| 31 | + std::chrono::milliseconds keep_alive_interval); | |
| 25 | 32 | ~Impl(); |
| 26 | 33 | |
| 27 | 34 | enum State |
| ... | ... | @@ -52,12 +59,13 @@ public: |
| 52 | 59 | State state = State::DISCONNECTED; ///< The current state of the client. |
| 53 | 60 | std::mutex state_mutex; ///< Mutex to protect state changes. |
| 54 | 61 | |
| 55 | - std::string host; ///< Host of the broker. | |
| 56 | - int port; ///< Port of the broker. | |
| 57 | - std::string client_id; ///< Client ID to use when connecting to the broker. | |
| 58 | - int connection_timeout; ///< Timeout in seconds for the connection to the broker. | |
| 59 | - int connection_backoff_max; ///< Maximum time between backoff attempts in seconds. | |
| 60 | - int keep_alive_interval; ///< Interval in seconds between keep-alive messages. | |
| 62 | + std::string host; ///< Host of the broker. | |
| 63 | + int port; ///< Port of the broker. | |
| 64 | + std::string client_id; ///< Client ID to use when connecting to the broker. | |
| 65 | + std::chrono::milliseconds connection_timeout; ///< Timeout in seconds for the connection to the broker. | |
| 66 | + std::chrono::milliseconds connection_backoff; ///< Backoff time when connection to the broker failed. This is doubled every time a connection fails, up till \ref connection_backoff_max. | |
| 67 | + std::chrono::milliseconds connection_backoff_max; ///< Maximum time between backoff attempts in seconds. | |
| 68 | + std::chrono::milliseconds keep_alive_interval; ///< Interval in seconds between keep-alive messages. | |
| 61 | 69 | |
| 62 | 70 | Client::LogLevel log_level = Client::LogLevel::NONE; ///< The log level to use. |
| 63 | 71 | std::function<void(Client::LogLevel, std::string)> logger = [](Client::LogLevel, std::string) { /* empty */ }; ///< Logger callback. | ... | ... |
src/Connection.cpp
| ... | ... | @@ -18,7 +18,8 @@ |
| 18 | 18 | |
| 19 | 19 | TrueMQTT::Client::Impl::Connection::Connection(Client::Impl &impl) |
| 20 | 20 | : m_impl(impl), |
| 21 | - m_thread(&Connection::run, this) | |
| 21 | + m_thread(&Connection::run, this), | |
| 22 | + m_backoff(impl.connection_backoff) | |
| 22 | 23 | { |
| 23 | 24 | } |
| 24 | 25 | |
| ... | ... | @@ -67,10 +68,16 @@ void TrueMQTT::Client::Impl::Connection::run() |
| 67 | 68 | break; |
| 68 | 69 | |
| 69 | 70 | case State::BACKOFF: |
| 70 | - LOG_WARNING(&m_impl, "Connection failed; will retry in NNN seconds"); | |
| 71 | + LOG_WARNING(&m_impl, "Connection failed; will retry in " + std::to_string(m_backoff.count()) + "ms"); | |
| 71 | 72 | |
| 72 | - // TODO: use the configuration | |
| 73 | - std::this_thread::sleep_for(std::chrono::seconds(5)); | |
| 73 | + std::this_thread::sleep_for(m_backoff); | |
| 74 | + | |
| 75 | + // Calculate the next backoff time, slowly reducing how often we retry. | |
| 76 | + m_backoff *= 2; | |
| 77 | + if (m_backoff > m_impl.connection_backoff_max) | |
| 78 | + { | |
| 79 | + m_backoff = m_impl.connection_backoff_max; | |
| 80 | + } | |
| 74 | 81 | |
| 75 | 82 | m_state = State::RESOLVING; |
| 76 | 83 | break; |
| ... | ... | @@ -96,6 +103,11 @@ void TrueMQTT::Client::Impl::Connection::run() |
| 96 | 103 | } |
| 97 | 104 | |
| 98 | 105 | case State::STOP: |
| 106 | + if (m_socket != INVALID_SOCKET) | |
| 107 | + { | |
| 108 | + closesocket(m_socket); | |
| 109 | + m_socket = INVALID_SOCKET; | |
| 110 | + } | |
| 99 | 111 | return; |
| 100 | 112 | } |
| 101 | 113 | } |
| ... | ... | @@ -243,8 +255,7 @@ bool TrueMQTT::Client::Impl::Connection::connectToAny() |
| 243 | 255 | } |
| 244 | 256 | |
| 245 | 257 | // Check if it is more than the timeout ago since we last tried a connection. |
| 246 | - // TODO -- Used to configured value | |
| 247 | - if (std::chrono::steady_clock::now() < m_last_attempt + std::chrono::seconds(10)) | |
| 258 | + if (std::chrono::steady_clock::now() < m_last_attempt + m_impl.connection_timeout) | |
| 248 | 259 | { |
| 249 | 260 | return true; |
| 250 | 261 | } |
| ... | ... | @@ -317,11 +328,13 @@ bool TrueMQTT::Client::Impl::Connection::connectToAny() |
| 317 | 328 | LOG_WARNING(&m_impl, "Could not set socket to non-blocking; expect performance impact"); |
| 318 | 329 | } |
| 319 | 330 | |
| 331 | + m_backoff = m_impl.connection_backoff; | |
| 332 | + m_socket = socket_connected; | |
| 333 | + | |
| 320 | 334 | // Only change the state if no disconnect() has been requested in the mean time. |
| 321 | 335 | if (m_state != State::STOP) |
| 322 | 336 | { |
| 323 | 337 | m_state = State::AUTHENTICATING; |
| 324 | - m_socket = socket_connected; | |
| 325 | 338 | sendConnect(); |
| 326 | 339 | } |
| 327 | 340 | return true; | ... | ... |
src/Connection.h
| ... | ... | @@ -9,6 +9,7 @@ |
| 9 | 9 | |
| 10 | 10 | #include "ClientImpl.h" |
| 11 | 11 | |
| 12 | +#include <chrono> | |
| 12 | 13 | #include <string> |
| 13 | 14 | #include <map> |
| 14 | 15 | #include <netdb.h> |
| ... | ... | @@ -56,14 +57,19 @@ private: |
| 56 | 57 | |
| 57 | 58 | TrueMQTT::Client::Impl &m_impl; |
| 58 | 59 | |
| 59 | - State m_state = State::RESOLVING; | |
| 60 | - std::thread m_thread; ///< Current thread used to run this connection. | |
| 60 | + State m_state = State::RESOLVING; ///< Current state of the connection. | |
| 61 | + std::thread m_thread; ///< Current thread used to run this connection. | |
| 62 | + | |
| 63 | + std::chrono::milliseconds m_backoff; ///< Current backoff time. | |
| 64 | + | |
| 65 | + addrinfo *m_host_resolved = nullptr; ///< Address info of the hostname, once looked up. | |
| 66 | + std::vector<addrinfo *> m_addresses = {}; ///< List of addresses to try to connect to. | |
| 61 | 67 | |
| 62 | - addrinfo *m_host_resolved = nullptr; ///< Address info of the hostname, once looked up. | |
| 63 | - std::vector<addrinfo *> m_addresses = {}; ///< List of addresses to try to connect to. | |
| 64 | 68 | size_t m_address_current = 0; ///< Index of the address we are currently trying to connect to. |
| 65 | 69 | std::chrono::steady_clock::time_point m_last_attempt = {}; ///< Time of the last attempt to connect to the current address. |
| 66 | - std::vector<SOCKET> m_sockets = {}; ///< List of sockets we are currently trying to connect to. | |
| 67 | - std::map<SOCKET, addrinfo *> m_socket_to_address = {}; ///< Map of sockets to the address they are trying to connect to. | |
| 68 | - SOCKET m_socket = INVALID_SOCKET; ///< The socket we are currently connected with, or INVALID_SOCKET if not connected. | |
| 70 | + | |
| 71 | + std::vector<SOCKET> m_sockets = {}; ///< List of sockets we are currently trying to connect to. | |
| 72 | + std::map<SOCKET, addrinfo *> m_socket_to_address = {}; ///< Map of sockets to the address they are trying to connect to. | |
| 73 | + | |
| 74 | + SOCKET m_socket = INVALID_SOCKET; ///< The socket we are currently connected with, or INVALID_SOCKET if not connected. | |
| 69 | 75 | }; | ... | ... |