// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** #include "init_curl_libssh2.h" #include #include //DON'T include directly! #include //DON'T include directly! using namespace zen; namespace { int uniInitLevel = 0; //support interleaving initialization calls! (e.g. use for libssh2 and libcurl) //zero-initialized POD => not subject to static initialization order fiasco void libsshCurlUnifiedInit() { assert(runningOnMainThread()); assert(uniInitLevel >= 0); if (++uniInitLevel != 1) //non-atomic => require call from main thread return; libcurlInit(); //includes WSAStartup() also needed by libssh2 [[maybe_unused]] const int rc = ::libssh2_init(0); //includes OpenSSL-related initialization which might be needed (and hopefully won't hurt...) assert(rc == 0); //libssh2 unconditionally returns 0 => why then have a return value in first place??? } void libsshCurlUnifiedTearDown() { assert(runningOnMainThread()); assert(uniInitLevel >= 1); if (--uniInitLevel != 0) return; ::libssh2_exit(); libcurlTearDown(); } } class zen::UniSessionCounter::Impl { public: void inc() //throw SysError { { std::unique_lock dummy(lockCount_); assert(sessionCount_ >= 0); if (!newSessionsAllowed_) throw SysError(formatSystemError("UniSessionCounter::inc", L"", L"Function call not allowed during init/shutdown.")); ++sessionCount_; } conditionCountChanged_.notify_all(); } void dec() //noexcept { { std::unique_lock dummy(lockCount_); assert(sessionCount_ >= 1); --sessionCount_; } conditionCountChanged_.notify_all(); } void onInitCompleted() //noexcept { std::unique_lock dummy(lockCount_); newSessionsAllowed_ = true; } void onBeforeTearDown() //noexcept { std::unique_lock dummy(lockCount_); newSessionsAllowed_ = false; conditionCountChanged_.wait(dummy, [this] { return sessionCount_ == 0; }); } Impl() {} ~Impl() { } private: Impl (const Impl&) = delete; Impl& operator=(const Impl&) = delete; std::mutex lockCount_; int sessionCount_ = 0; std::condition_variable conditionCountChanged_; bool newSessionsAllowed_ = false; }; UniSessionCounter::UniSessionCounter() : pimpl(std::make_unique()) {} UniSessionCounter::~UniSessionCounter() {} std::unique_ptr zen::createUniSessionCounter() { return std::make_unique(); } class zen::UniCounterCookie { public: UniCounterCookie(const std::shared_ptr& sessionCounter) : sessionCounter_(sessionCounter) {} ~UniCounterCookie() { sessionCounter_->pimpl->dec(); } private: UniCounterCookie (const UniCounterCookie&) = delete; UniCounterCookie& operator=(const UniCounterCookie&) = delete; const std::shared_ptr sessionCounter_; }; std::shared_ptr zen::getLibsshCurlUnifiedInitCookie(Global& globalSftpSessionCount) //throw SysError { std::shared_ptr sessionCounter = globalSftpSessionCount.get(); if (!sessionCounter) throw SysError(formatSystemError("getLibsshCurlUnifiedInitCookie", L"", L"Function call not allowed during init/shutdown.")); //=> ~UniCounterCookie() *not* called! sessionCounter->pimpl->inc(); //throw SysError // //pass "ownership" of having to call UniSessionCounter::dec() return std::make_shared(sessionCounter); //throw SysError } UniInitializer::UniInitializer(UniSessionCounter& sessionCount) : sessionCount_(sessionCount) { libsshCurlUnifiedInit(); sessionCount_.pimpl->onInitCompleted(); } UniInitializer::~UniInitializer() { //wait until all (S)FTP sessions running on detached threads have ended! otherwise they'll crash during ::WSACleanup()! sessionCount_.pimpl->onBeforeTearDown(); /* alternatively we could use a Global and have each session own a shared_ptr: drawback 1: SFTP clean-up may happen on worker thread => probably not supported!!! drawback 2: cleanup will not happen when the C++ runtime on Windows kills all worker threads during shutdown */ libsshCurlUnifiedTearDown(); }