loolwsd/DocumentBroker.cpp | 9 +- loolwsd/DocumentBroker.hpp | 6 + loolwsd/LOOLWSD.cpp | 147 ++++++++++++++++++++++----------------------- 3 files changed, 81 insertions(+), 81 deletions(-)
New commits: commit 3993757ee806d7bdde2408852f6227b8f848f1d8 Author: Ashod Nakashian <ashod.nakash...@collabora.co.uk> Date: Sat Nov 5 22:15:44 2016 -0400 loolwsd: fix race while creating new documents This fixes the race rather than trying to patch it. It still minimizes the locking necessary to a minimum to maximize parallelism. The approach is to have at least the DocBrokers lock or the DocumentBroker lock (if we already have a doc) while creating new document views. Change-Id: I96b4f17b3be3d03cd5e6f4d17d39e2165fe008a7 Reviewed-on: https://gerrit.libreoffice.org/30628 Reviewed-by: Ashod Nakashian <ashnak...@gmail.com> Tested-by: Ashod Nakashian <ashnak...@gmail.com> diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp index 97e21b4..19dbb97 100644 --- a/loolwsd/DocumentBroker.cpp +++ b/loolwsd/DocumentBroker.cpp @@ -358,9 +358,10 @@ bool DocumentBroker::save(const std::string& sessionId, bool success, const std: return false; } -bool DocumentBroker::autoSave(const bool force, const size_t waitTimeoutMs) +bool DocumentBroker::autoSave(const bool force, const size_t waitTimeoutMs, std::unique_lock<std::mutex>& lock) { - std::unique_lock<std::mutex> lock(_mutex); + Util::assertIsLocked(lock); + if (_sessions.empty() || _storage == nullptr || !_isLoaded || !_childProcess->isAlive() || (!_isModified && !force)) { @@ -549,7 +550,7 @@ bool DocumentBroker::connectPeers(std::shared_ptr<PrisonerSession>& session) size_t DocumentBroker::removeSession(const std::string& id) { - std::lock_guard<std::mutex> lock(_mutex); + Util::assertIsLocked(_mutex); auto it = _sessions.find(id); if (it != _sessions.end()) @@ -804,7 +805,7 @@ void DocumentBroker::handleTileCombinedResponse(const std::vector<char>& payload bool DocumentBroker::startDestroy(const std::string& id) { - std::unique_lock<std::mutex> lock(_mutex); + Util::assertIsLocked(_mutex); const auto currentSession = _sessions.find(id); assert(currentSession != _sessions.end()); diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp index 17de968..e58b642 100644 --- a/loolwsd/DocumentBroker.hpp +++ b/loolwsd/DocumentBroker.hpp @@ -195,7 +195,7 @@ public: /// complete before returning, or timeout. /// @return true if attempts to save or it also waits /// and receives save notification. Otherwise, false. - bool autoSave(const bool force, const size_t waitTimeoutMs); + bool autoSave(const bool force, const size_t waitTimeoutMs, std::unique_lock<std::mutex>& lock); Poco::URI getPublicUri() const { return _uriPublic; } Poco::URI getJailedUri() const { return _uriJailed; } @@ -206,7 +206,7 @@ public: bool isAlive() const { return _childProcess && _childProcess->isAlive(); } size_t getSessionsCount() const { - std::lock_guard<std::mutex> lock(_mutex); + Util::assertIsLocked(_mutex); return _sessions.size(); } @@ -273,6 +273,8 @@ public: /// Get the PID of the associated child process Poco::Process::PID getPid() const { return _childProcess->getPid(); } + std::unique_lock<std::mutex> getLock() { return std::unique_lock<std::mutex>(_mutex); } + private: /// Sends the .uno:Save command to LoKit. bool sendUnoSave(const bool dontSaveIfUnmodified); diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp index 9b91e1c..7ec35a1 100644 --- a/loolwsd/LOOLWSD.cpp +++ b/loolwsd/LOOLWSD.cpp @@ -271,6 +271,8 @@ static void forkChildren(const int number) /// Returns true if removed at least one. static bool cleanupChildren() { + Util::assertIsLocked(NewChildrenMutex); + bool removed = false; for (int i = NewChildren.size() - 1; i >= 0; --i) { @@ -558,6 +560,7 @@ private: } docBrokersLock.lock(); + auto docLock = docBroker->getLock(); sessionsCount = docBroker->removeSession(id); if (sessionsCount == 0) { @@ -723,93 +726,92 @@ private: cleanupDocBrokers(); // Lookup this document. - auto it = DocBrokers.find(docKey); - if (it != DocBrokers.end()) + auto it = DocBrokers.lower_bound(docKey); + if (it != DocBrokers.end() && it->first == docKey) { // Get the DocumentBroker from the Cache. Log::debug("Found DocumentBroker for docKey [" + docKey + "]."); docBroker = it->second; assert(docBroker); - } - else - { - // New Document. -#if MAX_DOCUMENTS > 0 - if (DocBrokers.size() + 1 > MAX_DOCUMENTS) - { - Log::error() << "Limit on maximum number of open documents of " - << MAX_DOCUMENTS << " reached." << Log::end; - shutdownLimitReached(*ws); - return; - } -#endif - - // Store a dummy (marked to destroy) document broker until we - // have the real one, so that the other requests block - Log::debug("Inserting a dummy DocumentBroker for docKey [" + docKey + "] temporarily."); - std::shared_ptr<DocumentBroker> tempBroker = std::make_shared<DocumentBroker>(); - DocBrokers.emplace(docKey, tempBroker); - } - - docBrokersLock.unlock(); - - if (docBroker && docBroker->isMarkedToDestroy()) - { - // If this document is going out, wait. - Log::debug("Document [" + docKey + "] is marked to destroy, waiting to reload."); - - bool timedOut = true; - for (size_t i = 0; i < COMMAND_TIMEOUT_MS / POLL_TIMEOUT_MS; ++i) + if (docBroker->isMarkedToDestroy()) { - std::this_thread::sleep_for(std::chrono::milliseconds(POLL_TIMEOUT_MS)); + // Let the waiting happen in parallel to new requests. + docBrokersLock.unlock(); + + // If this document is going out, wait. + Log::debug("Document [" + docKey + "] is marked to destroy, waiting to reload."); - std::unique_lock<std::mutex> lock(DocBrokersMutex); - it = DocBrokers.find(docKey); - if (it == DocBrokers.end()) + bool timedOut = true; + for (size_t i = 0; i < COMMAND_TIMEOUT_MS / POLL_TIMEOUT_MS; ++i) { - // went away successfully - docBroker.reset(); - Log::debug("Inserting a dummy DocumentBroker for docKey [" + docKey + "] temporarily after the other instance is gone."); + std::this_thread::sleep_for(std::chrono::milliseconds(POLL_TIMEOUT_MS)); - std::shared_ptr<DocumentBroker> tempBroker = std::make_shared<DocumentBroker>(); - DocBrokers.emplace(docKey, tempBroker); + docBrokersLock.lock(); + it = DocBrokers.find(docKey); + if (it == DocBrokers.end()) + { + // went away successfully + docBroker.reset(); + docBrokersLock.unlock(); + timedOut = false; + break; + } + else if (it->second && !it->second->isMarkedToDestroy()) + { + // was actually replaced by a real document + docBroker = it->second; + docBrokersLock.unlock(); + timedOut = false; + break; + } - timedOut = false; - break; + docBrokersLock.unlock(); + if (TerminationFlag) + { + Log::error("Termination flag set. Not loading new session [" + id + "]"); + return; + } } - else if (it->second && !it->second->isMarkedToDestroy()) + + if (timedOut) { - // was actually replaced by a real document - docBroker = it->second; - timedOut = false; - break; + // Still here, but marked to destroy. Proceed and hope to recover. + Log::error("Timed out while waiting for document to unload before loading."); } - if (TerminationFlag) + // Retake the lock and recheck if another thread created the DocBroker. + docBrokersLock.lock(); + it = DocBrokers.lower_bound(docKey); + if (it != DocBrokers.end() && it->first == docKey) { - Log::error("Termination flag set. Not loading new session [" + id + "]"); - return; + // Get the DocumentBroker from the Cache. + Log::debug("Found DocumentBroker for docKey [" + docKey + "]."); + docBroker = it->second; + assert(docBroker); } } - - if (timedOut) - { - // Still here, but marked to destroy. Proceed and hope to recover. - Log::error("Timed out while waiting for document to unload before loading."); - } } + Util::assertIsLocked(docBrokersLock); + if (TerminationFlag) { Log::error("Termination flag set. No loading new session [" + id + "]"); return; } - bool newDoc = false; if (!docBroker) { - newDoc = true; +#if MAX_DOCUMENTS > 0 + if (DocBrokers.size() + 1 > MAX_DOCUMENTS) + { + Log::error("Maximum number of open documents reached."); + shutdownLimitReached(*ws); + return; + } +#endif + // Request a kit process for this doc. auto child = getNewChild(); if (!child) @@ -823,28 +825,22 @@ private: Log::debug("New DocumentBroker for docKey [" + docKey + "]."); docBroker = std::make_shared<DocumentBroker>(uriPublic, docKey, LOOLWSD::ChildRoot, child); child->setDocumentBroker(docBroker); + DocBrokers.insert(it, std::make_pair(docKey, docBroker)); } + docBrokersLock.unlock(); + // Validate the broker. if (!docBroker || !docBroker->isAlive()) { - Log::error("DocBroker is invalid or premature termination of child process. Service Unavailable."); - if (!newDoc) - { - // Remove. - std::unique_lock<std::mutex> lock(DocBrokersMutex); - DocBrokers.erase(docKey); - } + // Cleanup later. + Log::error("DocBroker is invalid or premature termination of child " + "process. Service Unavailable."); + DocBrokers.erase(docKey); throw WebSocketErrorMessageException(SERVICE_UNAVAILABLE_INTERNAL_ERROR); } - if (newDoc) - { - std::unique_lock<std::mutex> lock(DocBrokersMutex); - DocBrokers[docKey] = docBroker; - } - // Check if readonly session is required bool isReadOnly = false; for (const auto& param : uriPublic.getQueryParameters()) @@ -893,7 +889,7 @@ private: // Connection terminated. Destroy session. Log::debug("Client session [" + id + "] terminated. Cleaning up."); { - std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex); + auto docLock = docBroker->getLock(); // We cannot destroy it, before save, if this is the last session. // Otherwise, we may end up removing the one and only session. @@ -918,7 +914,7 @@ private: // We need to wait until the save notification reaches us // and Storage persists the document. - if (!docBroker->autoSave(forceSave, COMMAND_TIMEOUT_MS)) + if (!docBroker->autoSave(forceSave, COMMAND_TIMEOUT_MS, docLock)) { Log::error("Auto-save before closing failed."); } @@ -2023,7 +2019,8 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/) cleanupDocBrokers(); for (auto& pair : DocBrokers) { - pair.second->autoSave(false, 0); + auto docLock = pair.second->getLock(); + pair.second->autoSave(false, 0, docLock); } } catch (const std::exception& exc) _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits