Commit 96c1dd91cf5580d83d80d6903067e4b24765ef7b
1 parent
4cd9300c
Queue session removals
This replaces the old style checking of expired sessions once every 10 minutes or so.
Showing
5 changed files
with
94 additions
and
52 deletions
client.cpp
| ... | ... | @@ -69,10 +69,13 @@ Client::~Client() |
| 69 | 69 | close(fd); |
| 70 | 70 | } |
| 71 | 71 | |
| 72 | - // MQTT-3.1.2-6 | |
| 73 | 72 | if (session->getDestroyOnDisconnect()) |
| 74 | 73 | { |
| 75 | - store->removeSession(clientid); | |
| 74 | + store->removeSession(session); | |
| 75 | + } | |
| 76 | + else | |
| 77 | + { | |
| 78 | + store->queueSessionRemoval(session); | |
| 76 | 79 | } |
| 77 | 80 | } |
| 78 | 81 | ... | ... |
subscriptionstore.cpp
| ... | ... | @@ -269,24 +269,9 @@ bool SubscriptionStore::sessionPresent(const std::string &clientid) |
| 269 | 269 | return result; |
| 270 | 270 | } |
| 271 | 271 | |
| 272 | -/** | |
| 273 | - * @brief SubscriptionStore::purgeEmptyWills doesn't lock a mutex, because it's a helper for elsewhere. | |
| 274 | - */ | |
| 275 | -void SubscriptionStore::purgeEmptyWills() | |
| 276 | -{ | |
| 277 | - auto it = pendingWillMessages.begin(); | |
| 278 | - while (it != pendingWillMessages.end()) | |
| 279 | - { | |
| 280 | - std::shared_ptr<Publish> p = (*it).lock(); | |
| 281 | - if (!p) | |
| 282 | - { | |
| 283 | - it = pendingWillMessages.erase(it); | |
| 284 | - } | |
| 285 | - } | |
| 286 | -} | |
| 287 | - | |
| 288 | 272 | void SubscriptionStore::sendQueuedWillMessages() |
| 289 | 273 | { |
| 274 | + const auto now = std::chrono::steady_clock::now(); | |
| 290 | 275 | std::lock_guard<std::mutex>(this->pendingWillsMutex); |
| 291 | 276 | |
| 292 | 277 | auto it = pendingWillMessages.begin(); |
| ... | ... | @@ -295,7 +280,7 @@ void SubscriptionStore::sendQueuedWillMessages() |
| 295 | 280 | std::shared_ptr<Publish> p = (*it).lock(); |
| 296 | 281 | if (p) |
| 297 | 282 | { |
| 298 | - if (p->createdAt + std::chrono::seconds(p->will_delay) > std::chrono::steady_clock::now()) | |
| 283 | + if (p->createdAt + std::chrono::seconds(p->will_delay) > now) | |
| 299 | 284 | break; |
| 300 | 285 | |
| 301 | 286 | logger->logf(LOG_DEBUG, "Sending delayed will on topic '%s'.", p->topic.c_str() ); |
| ... | ... | @@ -306,14 +291,15 @@ void SubscriptionStore::sendQueuedWillMessages() |
| 306 | 291 | } |
| 307 | 292 | } |
| 308 | 293 | |
| 309 | -void SubscriptionStore::queueWillMessage(std::shared_ptr<Publish> &willMessage) | |
| 294 | +void SubscriptionStore::queueWillMessage(std::shared_ptr<Publish> &willMessage, bool forceNow) | |
| 310 | 295 | { |
| 311 | 296 | if (!willMessage) |
| 312 | 297 | return; |
| 313 | 298 | |
| 314 | - logger->logf(LOG_DEBUG, "Queueing will on topic '%s', with delay %d seconds.", willMessage->topic.c_str(), willMessage->will_delay ); | |
| 299 | + const int delay = forceNow ? 0 : willMessage->will_delay; | |
| 300 | + logger->logf(LOG_DEBUG, "Queueing will on topic '%s', with delay %d seconds.", willMessage->topic.c_str(), delay ); | |
| 315 | 301 | |
| 316 | - if (willMessage->will_delay == 0) | |
| 302 | + if (delay == 0) | |
| 317 | 303 | { |
| 318 | 304 | PublishCopyFactory factory(willMessage.get()); |
| 319 | 305 | queuePacketAtSubscribers(factory); |
| ... | ... | @@ -577,13 +563,20 @@ int SubscriptionNode::cleanSubscriptions() |
| 577 | 563 | return subscribers.size() + subscribersLeftInChildren; |
| 578 | 564 | } |
| 579 | 565 | |
| 580 | -void SubscriptionStore::removeSession(const std::string &clientid) | |
| 566 | +void SubscriptionStore::removeSession(const std::shared_ptr<Session> &session) | |
| 581 | 567 | { |
| 568 | + const std::string &clientid = session->getClientId(); | |
| 569 | + logger->logf(LOG_DEBUG, "Removing session of client '%s'.", clientid.c_str()); | |
| 570 | + | |
| 571 | + std::shared_ptr<Publish> &will = session->getWill(); | |
| 572 | + if (will) | |
| 573 | + { | |
| 574 | + queueWillMessage(will, true); | |
| 575 | + } | |
| 576 | + | |
| 582 | 577 | RWLockGuard lock_guard(&subscriptionsRwlock); |
| 583 | 578 | lock_guard.wrlock(); |
| 584 | 579 | |
| 585 | - logger->logf(LOG_DEBUG, "Removing session of client '%s'.", clientid.c_str()); | |
| 586 | - | |
| 587 | 580 | auto session_it = sessionsById.find(clientid); |
| 588 | 581 | if (session_it != sessionsById.end()) |
| 589 | 582 | { |
| ... | ... | @@ -600,37 +593,59 @@ void SubscriptionStore::removeExpiredSessionsClients() |
| 600 | 593 | { |
| 601 | 594 | logger->logf(LOG_DEBUG, "Cleaning out old sessions"); |
| 602 | 595 | |
| 603 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 604 | - lock_guard.wrlock(); | |
| 596 | + const std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now(); | |
| 605 | 597 | |
| 606 | - auto session_it = sessionsById.begin(); | |
| 607 | - while (session_it != sessionsById.end()) | |
| 608 | 598 | { |
| 609 | - std::shared_ptr<Session> &session = session_it->second; | |
| 599 | + std::lock_guard<std::mutex>(this->queuedSessionRemovalsMutex); | |
| 610 | 600 | |
| 611 | - if (session->hasExpired()) | |
| 601 | + auto it = queuedSessionRemovals.begin(); | |
| 602 | + while (it != queuedSessionRemovals.end()) | |
| 612 | 603 | { |
| 613 | - logger->logf(LOG_DEBUG, "Removing expired session from store %s", session->getClientId().c_str()); | |
| 614 | - std::shared_ptr<Publish> &will = session->getWill(); | |
| 615 | - if (will) | |
| 604 | + QueuedSessionRemoval &qsr = *it; | |
| 605 | + std::shared_ptr<Session> session = (*it).getSession(); | |
| 606 | + if (session) | |
| 616 | 607 | { |
| 617 | - will->will_delay = 0; | |
| 618 | - queueWillMessage(will); | |
| 608 | + if (qsr.getExpiresAt() > now) | |
| 609 | + { | |
| 610 | + logger->logf(LOG_DEBUG, "Breaking from sorted list of queued session removals. %d left in the future.", queuedSessionRemovals.size()); | |
| 611 | + break; | |
| 612 | + } | |
| 613 | + | |
| 614 | + // A session could have been picked up again, so we have to verify its expiration status. | |
| 615 | + if (session->hasExpired()) | |
| 616 | + { | |
| 617 | + removeSession(session); | |
| 618 | + } | |
| 619 | 619 | } |
| 620 | - session_it = sessionsById.erase(session_it); | |
| 620 | + it = queuedSessionRemovals.erase(it); | |
| 621 | 621 | } |
| 622 | - else | |
| 623 | - session_it++; | |
| 624 | 622 | } |
| 625 | 623 | |
| 626 | - if (lastTreeCleanup + std::chrono::minutes(30) < std::chrono::steady_clock::now()) | |
| 624 | + if (lastTreeCleanup + std::chrono::minutes(30) < now) | |
| 627 | 625 | { |
| 626 | + RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 627 | + lock_guard.wrlock(); | |
| 628 | + | |
| 628 | 629 | logger->logf(LOG_NOTICE, "Rebuilding subscription tree"); |
| 629 | 630 | root.cleanSubscriptions(); |
| 630 | - lastTreeCleanup = std::chrono::steady_clock::now(); | |
| 631 | + lastTreeCleanup = now; | |
| 631 | 632 | } |
| 632 | 633 | } |
| 633 | 634 | |
| 635 | +void SubscriptionStore::queueSessionRemoval(const std::shared_ptr<Session> &session) | |
| 636 | +{ | |
| 637 | + QueuedSessionRemoval qsr(session); | |
| 638 | + | |
| 639 | + auto comp = [](const QueuedSessionRemoval &a, const QueuedSessionRemoval &b) | |
| 640 | + { | |
| 641 | + return a.getExpiresAt() < b.getExpiresAt(); | |
| 642 | + }; | |
| 643 | + | |
| 644 | + std::lock_guard<std::mutex>(this->queuedSessionRemovalsMutex); | |
| 645 | + auto pos = std::upper_bound(this->queuedSessionRemovals.begin(), this->queuedSessionRemovals.end(), qsr, comp); | |
| 646 | + this->queuedSessionRemovals.insert(pos, qsr); | |
| 647 | +} | |
| 648 | + | |
| 634 | 649 | int64_t SubscriptionStore::getRetainedMessageCount() const |
| 635 | 650 | { |
| 636 | 651 | return retainedMessageCount; |
| ... | ... | @@ -927,3 +942,20 @@ RetainedMessageNode *RetainedMessageNode::getChildren(const std::string &subtopi |
| 927 | 942 | return it->second.get(); |
| 928 | 943 | return nullptr; |
| 929 | 944 | } |
| 945 | + | |
| 946 | +QueuedSessionRemoval::QueuedSessionRemoval(const std::shared_ptr<Session> &session) : | |
| 947 | + session(session), | |
| 948 | + expiresAt(std::chrono::steady_clock::now() + std::chrono::seconds(session->getSessionExpiryInterval())) | |
| 949 | +{ | |
| 950 | + | |
| 951 | +} | |
| 952 | + | |
| 953 | +std::chrono::time_point<std::chrono::steady_clock> QueuedSessionRemoval::getExpiresAt() const | |
| 954 | +{ | |
| 955 | + return this->expiresAt; | |
| 956 | +} | |
| 957 | + | |
| 958 | +std::shared_ptr<Session> QueuedSessionRemoval::getSession() const | |
| 959 | +{ | |
| 960 | + return session.lock(); | |
| 961 | +} | ... | ... |
subscriptionstore.h
| ... | ... | @@ -84,6 +84,17 @@ class RetainedMessageNode |
| 84 | 84 | RetainedMessageNode *getChildren(const std::string &subtopic) const; |
| 85 | 85 | }; |
| 86 | 86 | |
| 87 | +class QueuedSessionRemoval | |
| 88 | +{ | |
| 89 | + std::weak_ptr<Session> session; | |
| 90 | + std::chrono::time_point<std::chrono::steady_clock> expiresAt; | |
| 91 | + | |
| 92 | +public: | |
| 93 | + QueuedSessionRemoval(const std::shared_ptr<Session> &session); | |
| 94 | + std::chrono::time_point<std::chrono::steady_clock> getExpiresAt() const; | |
| 95 | + std::shared_ptr<Session> getSession() const; | |
| 96 | +}; | |
| 97 | + | |
| 87 | 98 | class SubscriptionStore |
| 88 | 99 | { |
| 89 | 100 | #ifdef TESTING |
| ... | ... | @@ -96,6 +107,9 @@ class SubscriptionStore |
| 96 | 107 | std::unordered_map<std::string, std::shared_ptr<Session>> sessionsById; |
| 97 | 108 | const std::unordered_map<std::string, std::shared_ptr<Session>> &sessionsByIdConst; |
| 98 | 109 | |
| 110 | + std::mutex queuedSessionRemovalsMutex; | |
| 111 | + std::list<QueuedSessionRemoval> queuedSessionRemovals; | |
| 112 | + | |
| 99 | 113 | pthread_rwlock_t retainedMessagesRwlock = PTHREAD_RWLOCK_INITIALIZER; |
| 100 | 114 | RetainedMessageNode retainedMessagesRoot; |
| 101 | 115 | RetainedMessageNode retainedMessagesRootDollar; |
| ... | ... | @@ -121,8 +135,6 @@ class SubscriptionStore |
| 121 | 135 | void countSubscriptions(SubscriptionNode *this_node, int64_t &count) const; |
| 122 | 136 | |
| 123 | 137 | SubscriptionNode *getDeepestNode(const std::string &topic, const std::vector<std::string> &subtopics); |
| 124 | - | |
| 125 | - void purgeEmptyWills(); | |
| 126 | 138 | public: |
| 127 | 139 | SubscriptionStore(); |
| 128 | 140 | |
| ... | ... | @@ -133,14 +145,14 @@ public: |
| 133 | 145 | bool sessionPresent(const std::string &clientid); |
| 134 | 146 | |
| 135 | 147 | void sendQueuedWillMessages(); |
| 136 | - void queueWillMessage(std::shared_ptr<Publish> &willMessage); | |
| 148 | + void queueWillMessage(std::shared_ptr<Publish> &willMessage, bool forceNow = false); | |
| 137 | 149 | void queuePacketAtSubscribers(PublishCopyFactory ©Factory, bool dollar = false); |
| 138 | 150 | uint64_t giveClientRetainedMessages(const std::shared_ptr<Client> &client, const std::shared_ptr<Session> &ses, |
| 139 | 151 | const std::vector<std::string> &subscribeSubtopics, char max_qos); |
| 140 | 152 | |
| 141 | 153 | void setRetainedMessage(const std::string &topic, const std::vector<std::string> &subtopics, const std::string &payload, char qos); |
| 142 | 154 | |
| 143 | - void removeSession(const std::string &clientid); | |
| 155 | + void removeSession(const std::shared_ptr<Session> &session); | |
| 144 | 156 | void removeExpiredSessionsClients(); |
| 145 | 157 | |
| 146 | 158 | int64_t getRetainedMessageCount() const; |
| ... | ... | @@ -152,6 +164,8 @@ public: |
| 152 | 164 | |
| 153 | 165 | void saveSessionsAndSubscriptions(const std::string &filePath); |
| 154 | 166 | void loadSessionsAndSubscriptions(const std::string &filePath); |
| 167 | + | |
| 168 | + void queueSessionRemoval(const std::shared_ptr<Session> &session); | |
| 155 | 169 | }; |
| 156 | 170 | |
| 157 | 171 | #endif // SUBSCRIPTIONSTORE_H | ... | ... |
types.cpp
| ... | ... | @@ -134,12 +134,6 @@ void PublishBase::setClientSpecificProperties() |
| 134 | 134 | propertyBuilder->writeMessageExpiryInterval(newExpiresAfter.count()); |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | -bool PublishBase::hasExpired() const | |
| 138 | -{ | |
| 139 | - auto now = std::chrono::steady_clock::now(); | |
| 140 | - return (createdAt + expiresAfter) > now; | |
| 141 | -} | |
| 142 | - | |
| 143 | 137 | Publish::Publish(const Publish &other) : |
| 144 | 138 | PublishBase(other) |
| 145 | 139 | { | ... | ... |
types.h
| ... | ... | @@ -209,7 +209,6 @@ public: |
| 209 | 209 | PublishBase(const std::string &topic, const std::string &payload, char qos); |
| 210 | 210 | size_t getLengthWithoutFixedHeader() const; |
| 211 | 211 | void setClientSpecificProperties(); |
| 212 | - bool hasExpired() const; | |
| 213 | 212 | }; |
| 214 | 213 | |
| 215 | 214 | class Publish : public PublishBase | ... | ... |