diff --git a/dependencies/opendht b/dependencies/opendht index 88d15be4ea8d8b827b8d0712a138ef6b0ce1d0ef..f2cee8e9ce24746caa7dee1847829c526d340284 160000 --- a/dependencies/opendht +++ b/dependencies/opendht @@ -1 +1 @@ -Subproject commit 88d15be4ea8d8b827b8d0712a138ef6b0ce1d0ef +Subproject commit f2cee8e9ce24746caa7dee1847829c526d340284 diff --git a/include/upnp/upnp_context.h b/include/upnp/upnp_context.h index ead135f2179b85e7807ab35fdf0988ed2dda7792..9941e3b24f4feedded35036d2210849f19c20de5 100644 --- a/include/upnp/upnp_context.h +++ b/include/upnp/upnp_context.h @@ -318,6 +318,7 @@ private: // Shutdown synchronization bool shutdownComplete_ {false}; + bool shutdownTimedOut_ {false}; }; } // namespace upnp diff --git a/src/upnp/protocol/pupnp/pupnp.cpp b/src/upnp/protocol/pupnp/pupnp.cpp index 8aa462dbaa4061722d58b519b034a8a02420c2a5..2c81e6740fbcc8874fa06e0798e925eb4849abb2 100644 --- a/src/upnp/protocol/pupnp/pupnp.cpp +++ b/src/upnp/protocol/pupnp/pupnp.cpp @@ -17,7 +17,6 @@ #include "pupnp.h" #include "string_utils.h" -#include <opendht/thread_pool.h> #include <opendht/http.h> namespace dhtnet { @@ -98,6 +97,7 @@ errorOnResponse(IXML_Document* doc, const std::shared_ptr<dht::log::Logger>& log PUPnP::PUPnP(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger) : UPnPProtocol(logger), ioContext(ctx), searchForIgdTimer_(*ctx) + , ongoingOpsThreadPool_(1, 64) { if (logger_) logger_->debug("PUPnP: Creating instance [{}] ...", fmt::ptr(this)); } @@ -188,15 +188,20 @@ PUPnP::getHostAddress() const } void -PUPnP::terminate(std::condition_variable& cv) +PUPnP::terminate() { if (logger_) logger_->debug("PUPnP: Terminate instance {}", fmt::ptr(this)); clientRegistered_ = false; observer_ = nullptr; - std::unique_lock lk(ongoingOpsMtx_); - destroying_ = true; - cvOngoing_.wait(lk, [&]() { return ongoingOps_ == 0; }); + { + std::lock_guard lk(ongoingOpsMtx_); + destroying_ = true; + if (ongoingOps_ > 0) { + if (logger_) logger_->debug("PUPnP: {} ongoing operations, detaching corresponding threads", ongoingOps_); + ongoingOpsThreadPool_.detach(); + } + } UpnpUnRegisterClient(ctrlptHandle_); @@ -214,25 +219,7 @@ PUPnP::terminate(std::condition_variable& cv) std::lock_guard lock(pupnpMutex_); validIgdList_.clear(); shutdownComplete_ = true; - cv.notify_one(); -} - -void -PUPnP::terminate() -{ - std::condition_variable cv {}; - ioContext->dispatch([&] { - terminate(cv); - }); - - std::unique_lock lk(pupnpMutex_); - if (cv.wait_for(lk, std::chrono::seconds(10), [this] { return shutdownComplete_; })) { - if (logger_) logger_->debug("PUPnP: Shutdown completed"); - } else { - if (logger_) logger_->error("PUPnP: Shutdown timed-out"); - // Force stop if the shutdown take too much time. - shutdownComplete_ = true; - } + if (logger_) logger_->debug("PUPnP: Instance {} terminated", fmt::ptr(this)); } void @@ -771,7 +758,7 @@ PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId, // Run a separate thread to prevent blocking this thread // if the IGD HTTP server is not responsive. - dht::ThreadPool::io().run([w = weak(), url=igdLocationUrl] { + ongoingOpsThreadPool_.run([w = weak(), url=igdLocationUrl] { if (auto upnpThis = w.lock()) { upnpThis->downLoadIgdDescription(url); } @@ -791,6 +778,13 @@ PUPnP::downLoadIgdDescription(const std::string& locationUrl) IXML_Document* doc_container_ptr = nullptr; int upnp_err = UpnpDownloadXmlDoc(locationUrl.c_str(), &doc_container_ptr); + std::lock_guard lk(ongoingOpsMtx_); + // Trying to use libupnp functions after UpnpFinish has been called (which may + // be the case if destroying_ is true) can cause errors. It's probably not a + // problem here, but return early just in case. + if (destroying_) + return; + if (upnp_err != UPNP_E_SUCCESS or not doc_container_ptr) { if(logger_) logger_->warn("PUPnP: Error downloading device XML document from {} -> {}", locationUrl, @@ -803,9 +797,7 @@ PUPnP::downLoadIgdDescription(const std::string& locationUrl) } }); } - std::lock_guard lk(ongoingOpsMtx_); ongoingOps_--; - cvOngoing_.notify_one(); } void diff --git a/src/upnp/protocol/pupnp/pupnp.h b/src/upnp/protocol/pupnp/pupnp.h index 7deb4fba43788627cc474271f96944ef3229eea7..03086ddc1f5ec09cd8106bebc1f60439104b96e3 100644 --- a/src/upnp/protocol/pupnp/pupnp.h +++ b/src/upnp/protocol/pupnp/pupnp.h @@ -26,6 +26,7 @@ #include "upnp_igd.h" #include "ip_utils.h" +#include <opendht/thread_pool.h> #include <upnp/upnp.h> #include <upnp/upnptools.h> @@ -246,9 +247,9 @@ private: // Count ongoing operations std::mutex ongoingOpsMtx_; - std::condition_variable cvOngoing_; int ongoingOps_ {0}; bool destroying_ {false}; + dht::ThreadPool ongoingOpsThreadPool_; }; } // namespace upnp diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp index cc54cb166db219a310a1cb5912da6f252a9e77eb..78488dce959d44cfd7a09f09e33034233e05d3c4 100644 --- a/src/upnp/upnp_context.cpp +++ b/src/upnp/upnp_context.cpp @@ -94,6 +94,12 @@ UPnPContext::shutdown(std::condition_variable& cv) controllerList_.clear(); protocolList_.clear(); shutdownComplete_ = true; + if (shutdownTimedOut_) { + // If we timed out in shutdown(), then calling notify_one is not necessary, + // and doing so anyway can cause bugs, see: + // https://git.jami.net/savoirfairelinux/dhtnet/-/issues/28 + return; + } cv.notify_one(); } @@ -110,7 +116,8 @@ UPnPContext::shutdown() if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) { if (logger_) logger_->debug("Shutdown completed"); } else { - if (logger_) logger_->error("Shutdown timed-out"); + if (logger_) logger_->error("Shutdown timed out"); + shutdownTimedOut_ = true; } // NOTE: It's important to unlock mappingMutex_ here, otherwise we get a // deadlock when the call to cv.wait_for() above times out before we return @@ -165,7 +172,7 @@ UPnPContext::startUpnp() void UPnPContext::stopUpnp(bool forceRelease) { - if (logger_) logger_->debug("Stopping UPNP context"); + if (logger_) logger_->debug("Stopping UPnP context"); // Clear all current mappings if any.