Commit d125dc4917664850d3bba2e9eedfffa76ebbf9f6
1 parent
efc917f9
Disconnect clients with good reason code
On server shutdown and when taking over a session. On disconnect, wills are queued first, we wait for the queueing to be done, then initiate disconnect. When TCP buffers are full and fds are not reported by epoll, the thread loop still exits and clients are just closed on exit.
Showing
8 changed files
with
97 additions
and
18 deletions
client.cpp
| ... | ... | @@ -407,6 +407,32 @@ void Client::sendOrQueueWill() |
| 407 | 407 | this->willPublish.reset(); |
| 408 | 408 | } |
| 409 | 409 | |
| 410 | +/** | |
| 411 | + * @brief Client::serverInitiatedDisconnect queues a disconnect packet and when the last bytes are written, the thread loop will disconnect it. | |
| 412 | + * @param reason is an MQTT5 reason code. | |
| 413 | + * | |
| 414 | + * There is a chance that an client's TCP buffers are full (when the client is gone, for example) and epoll will not report the | |
| 415 | + * fd as EPOLLOUT, which means the disconnect will not happen. It will then be up to the keep-alive mechanism to kick the client out. | |
| 416 | + * | |
| 417 | + * Sending clients disconnect packets is only supported by MQTT >= 5, so in case of MQTT3, just close the connection. | |
| 418 | + */ | |
| 419 | +void Client::serverInitiatedDisconnect(ReasonCodes reason) | |
| 420 | +{ | |
| 421 | + setDisconnectReason(formatString("Server initiating disconnect with reason code '%d'", static_cast<uint8_t>(reason))); | |
| 422 | + | |
| 423 | + if (this->protocolVersion >= ProtocolVersion::Mqtt5) | |
| 424 | + { | |
| 425 | + setReadyForDisconnect(); | |
| 426 | + Disconnect d(ProtocolVersion::Mqtt5, reason); | |
| 427 | + writeMqttPacket(d); | |
| 428 | + } | |
| 429 | + else | |
| 430 | + { | |
| 431 | + markAsDisconnecting(); | |
| 432 | + threadData->removeClientQueued(fd); | |
| 433 | + } | |
| 434 | +} | |
| 435 | + | |
| 410 | 436 | #ifndef NDEBUG |
| 411 | 437 | /** |
| 412 | 438 | * @brief IoWrapper::setFakeUpgraded(). | ... | ... |
client.h
mainapp.cpp
| ... | ... | @@ -285,9 +285,17 @@ void MainApp::queueRemoveExpiredSessions() |
| 285 | 285 | } |
| 286 | 286 | } |
| 287 | 287 | |
| 288 | -void MainApp::waitForAllThreadsQueuedWills() | |
| 288 | +void MainApp::waitForWillsQueued() | |
| 289 | 289 | { |
| 290 | - while(std::any_of(threads.begin(), threads.end(), [](std::shared_ptr<ThreadData> t){ return !t->allWilssSentForExit; })) | |
| 290 | + while(std::any_of(threads.begin(), threads.end(), [](std::shared_ptr<ThreadData> t){ return !t->allWillsQueued; })) | |
| 291 | + { | |
| 292 | + usleep(1000); | |
| 293 | + } | |
| 294 | +} | |
| 295 | + | |
| 296 | +void MainApp::waitForDisconnectsInitiated() | |
| 297 | +{ | |
| 298 | + while(std::any_of(threads.begin(), threads.end(), [](std::shared_ptr<ThreadData> t){ return !t->allDisconnectsSent; })) | |
| 291 | 299 | { |
| 292 | 300 | usleep(1000); |
| 293 | 301 | } |
| ... | ... | @@ -618,10 +626,16 @@ void MainApp::start() |
| 618 | 626 | logger->logf(LOG_DEBUG, "Having all client in all threads send or queue their will."); |
| 619 | 627 | for(std::shared_ptr<ThreadData> &thread : threads) |
| 620 | 628 | { |
| 621 | - thread->queueSendAllWills(); | |
| 629 | + thread->queueSendWills(); | |
| 622 | 630 | } |
| 631 | + waitForWillsQueued(); | |
| 623 | 632 | |
| 624 | - waitForAllThreadsQueuedWills(); | |
| 633 | + logger->logf(LOG_DEBUG, "Having all client in all threads send a disconnect packet."); | |
| 634 | + for(std::shared_ptr<ThreadData> &thread : threads) | |
| 635 | + { | |
| 636 | + thread->queueSendDisconnects(); | |
| 637 | + } | |
| 638 | + waitForDisconnectsInitiated(); | |
| 625 | 639 | |
| 626 | 640 | oneInstanceLock.unlock(); |
| 627 | 641 | ... | ... |
mainapp.h
| ... | ... | @@ -93,7 +93,8 @@ class MainApp |
| 93 | 93 | void saveStateInThread(); |
| 94 | 94 | void queueSendQueuedWills(); |
| 95 | 95 | void queueRemoveExpiredSessions(); |
| 96 | - void waitForAllThreadsQueuedWills(); | |
| 96 | + void waitForWillsQueued(); | |
| 97 | + void waitForDisconnectsInitiated(); | |
| 97 | 98 | |
| 98 | 99 | MainApp(const std::string &configFilePath); |
| 99 | 100 | public: | ... | ... |
subscriptionstore.cpp
| ... | ... | @@ -230,10 +230,7 @@ void SubscriptionStore::registerClientAndKickExistingOne(std::shared_ptr<Client> |
| 230 | 230 | { |
| 231 | 231 | logger->logf(LOG_NOTICE, "Disconnecting existing client with id '%s'", cl->getClientId().c_str()); |
| 232 | 232 | cl->setDisconnectReason("Another client with this ID connected"); |
| 233 | - | |
| 234 | - cl->setReadyForDisconnect(); | |
| 235 | - cl->getThreadData()->removeClientQueued(cl); | |
| 236 | - cl->markAsDisconnecting(); | |
| 233 | + cl->serverInitiatedDisconnect(ReasonCodes::SessionTakenOver); | |
| 237 | 234 | } |
| 238 | 235 | |
| 239 | 236 | } | ... | ... |
threaddata.cpp
| ... | ... | @@ -162,16 +162,39 @@ void ThreadData::removeExpiredSessions() |
| 162 | 162 | subscriptionStore->removeExpiredSessionsClients(); |
| 163 | 163 | } |
| 164 | 164 | |
| 165 | -void ThreadData::sendAllWils() | |
| 165 | +void ThreadData::sendAllWills() | |
| 166 | 166 | { |
| 167 | 167 | std::lock_guard<std::mutex> lck(clients_by_fd_mutex); |
| 168 | 168 | |
| 169 | - for(auto pairs : clients_by_fd) | |
| 169 | + for(auto &pair : clients_by_fd) | |
| 170 | 170 | { |
| 171 | - pairs.second->sendOrQueueWill(); | |
| 171 | + std::shared_ptr<Client> &c = pair.second; | |
| 172 | + c->sendOrQueueWill(); | |
| 172 | 173 | } |
| 173 | 174 | |
| 174 | - allWilssSentForExit = true; | |
| 175 | + allWillsQueued = true; | |
| 176 | +} | |
| 177 | + | |
| 178 | +void ThreadData::sendAllDisconnects() | |
| 179 | +{ | |
| 180 | + std::vector<std::shared_ptr<Client>> clientsFound; | |
| 181 | + | |
| 182 | + { | |
| 183 | + std::lock_guard<std::mutex> lck(clients_by_fd_mutex); | |
| 184 | + clientsFound.reserve(clients_by_fd.size()); | |
| 185 | + | |
| 186 | + for(auto &pair : clients_by_fd) | |
| 187 | + { | |
| 188 | + clientsFound.push_back(pair.second); | |
| 189 | + } | |
| 190 | + } | |
| 191 | + | |
| 192 | + for (std::shared_ptr<Client> &c : clientsFound) | |
| 193 | + { | |
| 194 | + c->serverInitiatedDisconnect(ReasonCodes::ServerShuttingDown); | |
| 195 | + } | |
| 196 | + | |
| 197 | + allDisconnectsSent = true; | |
| 175 | 198 | } |
| 176 | 199 | |
| 177 | 200 | void ThreadData::removeQueuedClients() |
| ... | ... | @@ -401,11 +424,21 @@ void ThreadData::authPluginPeriodicEvent() |
| 401 | 424 | authentication.periodicEvent(); |
| 402 | 425 | } |
| 403 | 426 | |
| 404 | -void ThreadData::queueSendAllWills() | |
| 427 | +void ThreadData::queueSendWills() | |
| 428 | +{ | |
| 429 | + std::lock_guard<std::mutex> locker(taskQueueMutex); | |
| 430 | + | |
| 431 | + auto f = std::bind(&ThreadData::sendAllWills, this); | |
| 432 | + taskQueue.push_front(f); | |
| 433 | + | |
| 434 | + wakeUpThread(); | |
| 435 | +} | |
| 436 | + | |
| 437 | +void ThreadData::queueSendDisconnects() | |
| 405 | 438 | { |
| 406 | 439 | std::lock_guard<std::mutex> locker(taskQueueMutex); |
| 407 | 440 | |
| 408 | - auto f = std::bind(&ThreadData::sendAllWils, this); | |
| 441 | + auto f = std::bind(&ThreadData::sendAllDisconnects, this); | |
| 409 | 442 | taskQueue.push_front(f); |
| 410 | 443 | |
| 411 | 444 | wakeUpThread(); | ... | ... |
threaddata.h
| ... | ... | @@ -66,7 +66,8 @@ class ThreadData |
| 66 | 66 | void publishStat(const std::string &topic, uint64_t n); |
| 67 | 67 | void sendQueuedWills(); |
| 68 | 68 | void removeExpiredSessions(); |
| 69 | - void sendAllWils(); | |
| 69 | + void sendAllWills(); | |
| 70 | + void sendAllDisconnects(); | |
| 70 | 71 | |
| 71 | 72 | void removeQueuedClients(); |
| 72 | 73 | |
| ... | ... | @@ -75,7 +76,8 @@ public: |
| 75 | 76 | Authentication authentication; |
| 76 | 77 | bool running = true; |
| 77 | 78 | bool finished = false; |
| 78 | - bool allWilssSentForExit = false; | |
| 79 | + bool allWillsQueued = false; | |
| 80 | + bool allDisconnectsSent = false; | |
| 79 | 81 | std::thread thread; |
| 80 | 82 | int threadnr = 0; |
| 81 | 83 | int epollfd = 0; |
| ... | ... | @@ -120,7 +122,8 @@ public: |
| 120 | 122 | void queueAuthPluginPeriodicEvent(); |
| 121 | 123 | void authPluginPeriodicEvent(); |
| 122 | 124 | |
| 123 | - void queueSendAllWills(); | |
| 125 | + void queueSendWills(); | |
| 126 | + void queueSendDisconnects(); | |
| 124 | 127 | }; |
| 125 | 128 | |
| 126 | 129 | #endif // THREADDATA_H | ... | ... |
threadloop.cpp
| ... | ... | @@ -144,6 +144,10 @@ void do_thread_work(ThreadData *threadData) |
| 144 | 144 | MqttPacket p(d); |
| 145 | 145 | client->writeMqttPacket(p); |
| 146 | 146 | client->setReadyForDisconnect(); |
| 147 | + | |
| 148 | + // When a client's TCP buffers are full (when the client is gone, for instance), EPOLLOUT will never be | |
| 149 | + // reported. In those cases, the client is not removed; not until the keep-alive mechanism anyway. Is | |
| 150 | + // that a problem? | |
| 147 | 151 | } |
| 148 | 152 | else |
| 149 | 153 | { | ... | ... |