Commit bc5029262e7294f75289fe2cb127d49108edf9b5
1 parent
ef845a8c
Reducing locking and memory overhead for saving sessions
Showing
4 changed files
with
40 additions
and
28 deletions
sessionsandsubscriptionsdb.cpp
| ... | ... | @@ -242,7 +242,7 @@ void SessionsAndSubscriptionsDB::writeRowHeader() |
| 242 | 242 | |
| 243 | 243 | } |
| 244 | 244 | |
| 245 | -void SessionsAndSubscriptionsDB::saveData(const std::vector<std::unique_ptr<Session>> &sessions, const std::unordered_map<std::string, std::list<SubscriptionForSerializing>> &subscriptions) | |
| 245 | +void SessionsAndSubscriptionsDB::saveData(const std::vector<std::shared_ptr<Session>> &sessions, const std::unordered_map<std::string, std::list<SubscriptionForSerializing>> &subscriptions) | |
| 246 | 246 | { |
| 247 | 247 | if (!f) |
| 248 | 248 | return; |
| ... | ... | @@ -254,12 +254,21 @@ void SessionsAndSubscriptionsDB::saveData(const std::vector<std::unique_ptr<Sess |
| 254 | 254 | logger->logf(LOG_DEBUG, "Saving current time stamp %ld", now_epoch); |
| 255 | 255 | writeInt64(now_epoch); |
| 256 | 256 | |
| 257 | - writeUint32(sessions.size()); | |
| 257 | + std::vector<std::shared_ptr<Session>> sessionsToSave; | |
| 258 | + // Sessions created with clean session need to be destroyed when disconnecting, so no point in saving them. | |
| 259 | + std::copy_if(sessions.begin(), sessions.end(), std::back_inserter(sessionsToSave), [](const std::shared_ptr<Session> &ses) { | |
| 260 | + return !ses->destroyOnDisconnect; | |
| 261 | + }); | |
| 262 | + | |
| 263 | + writeUint32(sessionsToSave.size()); | |
| 258 | 264 | |
| 259 | 265 | CirBuf cirbuf(1024); |
| 260 | 266 | |
| 261 | - for (const std::unique_ptr<Session> &ses : sessions) | |
| 267 | + for (const std::shared_ptr<Session> &_ses : sessionsToSave) | |
| 262 | 268 | { |
| 269 | + // Takes care of locking, and working on the snapshot/copy prevents doing disk IO under lock. | |
| 270 | + std::unique_ptr<Session> ses = _ses->getCopy(); | |
| 271 | + | |
| 263 | 272 | logger->logf(LOG_DEBUG, "Saving session '%s'.", ses->getClientId().c_str()); |
| 264 | 273 | |
| 265 | 274 | writeRowHeader(); | ... | ... |
sessionsandsubscriptionsdb.h
| ... | ... | @@ -68,7 +68,7 @@ public: |
| 68 | 68 | void openWrite(); |
| 69 | 69 | void openRead(); |
| 70 | 70 | |
| 71 | - void saveData(const std::vector<std::unique_ptr<Session>> &sessions, const std::unordered_map<std::string, std::list<SubscriptionForSerializing>> &subscriptions); | |
| 71 | + void saveData(const std::vector<std::shared_ptr<Session>> &sessions, const std::unordered_map<std::string, std::list<SubscriptionForSerializing>> &subscriptions); | |
| 72 | 72 | SessionsAndSubscriptionsResult readData(); |
| 73 | 73 | }; |
| 74 | 74 | ... | ... |
subscriptionstore.cpp
| ... | ... | @@ -134,7 +134,7 @@ SubscriptionNode *SubscriptionStore::getDeepestNode(const std::string &topic, co |
| 134 | 134 | |
| 135 | 135 | void SubscriptionStore::addSubscription(std::shared_ptr<Client> &client, const std::string &topic, const std::vector<std::string> &subtopics, char qos) |
| 136 | 136 | { |
| 137 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 137 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 138 | 138 | lock_guard.wrlock(); |
| 139 | 139 | |
| 140 | 140 | SubscriptionNode *deepestNode = getDeepestNode(topic, subtopics); |
| ... | ... | @@ -160,7 +160,7 @@ void SubscriptionStore::removeSubscription(std::shared_ptr<Client> &client, cons |
| 160 | 160 | if (topic.length() > 0 && topic[0] == '$') |
| 161 | 161 | deepestNode = &rootDollar; |
| 162 | 162 | |
| 163 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 163 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 164 | 164 | lock_guard.wrlock(); |
| 165 | 165 | |
| 166 | 166 | // This code looks like that for addSubscription(), but it's specifically different in that we don't want to default-create non-existing |
| ... | ... | @@ -227,7 +227,7 @@ void SubscriptionStore::registerClientAndKickExistingOne(std::shared_ptr<Client> |
| 227 | 227 | { |
| 228 | 228 | ThreadGlobals::getThreadData()->queueClientNextKeepAliveCheckLocked(client, true); |
| 229 | 229 | |
| 230 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 230 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 231 | 231 | lock_guard.wrlock(); |
| 232 | 232 | |
| 233 | 233 | if (client->getClientId().empty()) |
| ... | ... | @@ -274,7 +274,7 @@ void SubscriptionStore::registerClientAndKickExistingOne(std::shared_ptr<Client> |
| 274 | 274 | */ |
| 275 | 275 | std::shared_ptr<Session> SubscriptionStore::lockSession(const std::string &clientid) |
| 276 | 276 | { |
| 277 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 277 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 278 | 278 | lock_guard.rdlock(); |
| 279 | 279 | |
| 280 | 280 | auto it = sessionsByIdConst.find(clientid); |
| ... | ... | @@ -463,7 +463,7 @@ void SubscriptionStore::queuePacketAtSubscribers(PublishCopyFactory &copyFactory |
| 463 | 463 | |
| 464 | 464 | { |
| 465 | 465 | const std::vector<std::string> &subtopics = copyFactory.getSubtopics(); |
| 466 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 466 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 467 | 467 | lock_guard.rdlock(); |
| 468 | 468 | publishRecursively(subtopics.begin(), subtopics.end(), startNode, subscriberSessions); |
| 469 | 469 | } |
| ... | ... | @@ -649,7 +649,7 @@ void SubscriptionStore::removeSession(const std::shared_ptr<Session> &session) |
| 649 | 649 | queueWillMessage(will, session, true); |
| 650 | 650 | } |
| 651 | 651 | |
| 652 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 652 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 653 | 653 | lock_guard.wrlock(); |
| 654 | 654 | |
| 655 | 655 | auto session_it = sessionsById.find(clientid); |
| ... | ... | @@ -724,7 +724,7 @@ void SubscriptionStore::removeExpiredSessionsClients() |
| 724 | 724 | |
| 725 | 725 | if (lastTreeCleanup + std::chrono::minutes(30) < now) |
| 726 | 726 | { |
| 727 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 727 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 728 | 728 | lock_guard.wrlock(); |
| 729 | 729 | |
| 730 | 730 | logger->logf(LOG_NOTICE, "Rebuilding subscription tree"); |
| ... | ... | @@ -764,7 +764,7 @@ int64_t SubscriptionStore::getSubscriptionCount() |
| 764 | 764 | { |
| 765 | 765 | int64_t count = 0; |
| 766 | 766 | |
| 767 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 767 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 768 | 768 | lock_guard.rdlock(); |
| 769 | 769 | |
| 770 | 770 | countSubscriptions(&root, count); |
| ... | ... | @@ -902,38 +902,41 @@ void SubscriptionStore::loadRetainedMessages(const std::string &filePath) |
| 902 | 902 | |
| 903 | 903 | void SubscriptionStore::saveSessionsAndSubscriptions(const std::string &filePath) |
| 904 | 904 | { |
| 905 | - logger->logf(LOG_INFO, "Saving sessions and subscriptions to '%s'", filePath.c_str()); | |
| 905 | + logger->logf(LOG_INFO, "Saving sessions and subscriptions to '%s' in thread.", filePath.c_str()); | |
| 906 | 906 | |
| 907 | - std::vector<std::unique_ptr<Session>> sessionCopies; | |
| 907 | + const std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now(); | |
| 908 | + | |
| 909 | + std::vector<std::shared_ptr<Session>> sessionPointers; | |
| 908 | 910 | std::unordered_map<std::string, std::list<SubscriptionForSerializing>> subscriptionCopies; |
| 909 | 911 | |
| 910 | 912 | { |
| 911 | - RWLockGuard lock_guard(&subscriptionsRwlock); | |
| 913 | + RWLockGuard lock_guard(&sessionsAndSubscriptionsRwlock); | |
| 912 | 914 | lock_guard.rdlock(); |
| 913 | 915 | |
| 914 | - sessionCopies.reserve(sessionsByIdConst.size()); | |
| 916 | + sessionPointers.reserve(sessionsByIdConst.size()); | |
| 915 | 917 | |
| 916 | 918 | for (const auto &pair : sessionsByIdConst) |
| 917 | 919 | { |
| 918 | - const Session &org = *pair.second.get(); | |
| 919 | - | |
| 920 | - // Sessions created with clean session need to be destroyed when disconnecting, so no point in saving them. | |
| 921 | - if (org.getDestroyOnDisconnect()) | |
| 922 | - continue; | |
| 923 | - | |
| 924 | - sessionCopies.push_back(org.getCopy()); | |
| 920 | + sessionPointers.push_back(pair.second); | |
| 925 | 921 | } |
| 926 | 922 | |
| 927 | 923 | getSubscriptions(&root, "", true, subscriptionCopies); |
| 928 | 924 | } |
| 929 | 925 | |
| 930 | - // Then write the copies to disk, after having released the lock | |
| 926 | + const std::chrono::time_point<std::chrono::steady_clock> doneCopying = std::chrono::steady_clock::now(); | |
| 931 | 927 | |
| 932 | - logger->logf(LOG_DEBUG, "Collected %ld sessions and %ld subscriptions to save.", sessionCopies.size(), subscriptionCopies.size()); | |
| 928 | + const std::chrono::milliseconds copyDuration = std::chrono::duration_cast<std::chrono::milliseconds>(doneCopying - start); | |
| 929 | + logger->logf(LOG_DEBUG, "Collected %ld sessions and %ld subscriptions to save in %ld ms.", sessionPointers.size(), subscriptionCopies.size(), copyDuration.count()); | |
| 933 | 930 | |
| 934 | 931 | SessionsAndSubscriptionsDB db(filePath); |
| 935 | 932 | db.openWrite(); |
| 936 | - db.saveData(sessionCopies, subscriptionCopies); | |
| 933 | + db.saveData(sessionPointers, subscriptionCopies); | |
| 934 | + | |
| 935 | + const std::chrono::time_point<std::chrono::steady_clock> doneSaving = std::chrono::steady_clock::now(); | |
| 936 | + | |
| 937 | + const std::chrono::milliseconds saveDuration = std::chrono::duration_cast<std::chrono::milliseconds>(doneSaving - doneCopying); | |
| 938 | + logger->logf(LOG_INFO, "Saved %ld sessions and %ld subscriptions to '%s' in %ld ms.", | |
| 939 | + sessionPointers.size(), subscriptionCopies.size(), filePath.c_str(), saveDuration.count()); | |
| 937 | 940 | } |
| 938 | 941 | |
| 939 | 942 | void SubscriptionStore::loadSessionsAndSubscriptions(const std::string &filePath) |
| ... | ... | @@ -946,7 +949,7 @@ void SubscriptionStore::loadSessionsAndSubscriptions(const std::string &filePath |
| 946 | 949 | db.openRead(); |
| 947 | 950 | SessionsAndSubscriptionsResult loadedData = db.readData(); |
| 948 | 951 | |
| 949 | - RWLockGuard locker(&subscriptionsRwlock); | |
| 952 | + RWLockGuard locker(&sessionsAndSubscriptionsRwlock); | |
| 950 | 953 | locker.wrlock(); |
| 951 | 954 | |
| 952 | 955 | for (std::shared_ptr<Session> &session : loadedData.sessions) | ... | ... |
subscriptionstore.h
| ... | ... | @@ -106,7 +106,7 @@ class SubscriptionStore |
| 106 | 106 | |
| 107 | 107 | SubscriptionNode root; |
| 108 | 108 | SubscriptionNode rootDollar; |
| 109 | - pthread_rwlock_t subscriptionsRwlock = PTHREAD_RWLOCK_INITIALIZER; | |
| 109 | + pthread_rwlock_t sessionsAndSubscriptionsRwlock = PTHREAD_RWLOCK_INITIALIZER; | |
| 110 | 110 | std::unordered_map<std::string, std::shared_ptr<Session>> sessionsById; |
| 111 | 111 | const std::unordered_map<std::string, std::shared_ptr<Session>> &sessionsByIdConst; |
| 112 | 112 | ... | ... |