diff --git a/Makefile.am b/Makefile.am index 32da2b6ba..3363e337f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -236,6 +236,7 @@ endif CURL_SOURCES = \ src/lib/curl/Version.cxx src/lib/curl/Version.hxx \ + src/lib/curl/Init.cxx src/lib/curl/Init.hxx \ src/lib/curl/Global.cxx src/lib/curl/Global.hxx \ src/lib/curl/Request.cxx src/lib/curl/Request.hxx \ src/lib/curl/Handler.hxx \ diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index ed66e70cd..a1be25d90 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -21,6 +21,7 @@ #include "CurlInputPlugin.hxx" #include "lib/curl/Easy.hxx" #include "lib/curl/Global.hxx" +#include "lib/curl/Init.hxx" #include "lib/curl/Request.hxx" #include "lib/curl/Handler.hxx" #include "lib/curl/Slist.hxx" @@ -132,7 +133,7 @@ static unsigned proxy_port; static bool verify_peer, verify_host; -static CurlGlobal *curl_global; +static CurlInit *curl_init; static constexpr Domain curl_domain("curl"); @@ -165,7 +166,7 @@ CurlInputStream::FreeEasyIndirect() { BlockingCall(GetEventLoop(), [this](){ FreeEasy(); - curl_global->InvalidateSockets(); + (*curl_init)->InvalidateSockets(); }); } @@ -286,9 +287,12 @@ CurlInputStream::OnError(std::exception_ptr e) static void input_curl_init(EventLoop &event_loop, const ConfigBlock &block) { - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); - if (code != CURLE_OK) - throw PluginUnavailable(curl_easy_strerror(code)); + try { + curl_init = new CurlInit(event_loop); + } catch (const std::runtime_error &e) { + LogError(e); + throw PluginUnavailable(e.what()); + } const auto version_info = curl_version_info(CURLVERSION_FIRST); if (version_info != nullptr) { @@ -316,28 +320,15 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block) verify_peer = block.GetBlockValue("verify_peer", true); verify_host = block.GetBlockValue("verify_host", true); - - try { - curl_global = new CurlGlobal(event_loop); - } catch (const std::runtime_error &e) { - LogError(e); - curl_slist_free_all(http_200_aliases); - curl_global_cleanup(); - throw PluginUnavailable("curl_multi_init() failed"); - } } static void input_curl_finish(void) { - BlockingCall(curl_global->GetEventLoop(), [](){ - delete curl_global; - }); + delete curl_init; curl_slist_free_all(http_200_aliases); http_200_aliases = nullptr; - - curl_global_cleanup(); } CurlInputStream::~CurlInputStream() @@ -348,7 +339,7 @@ CurlInputStream::~CurlInputStream() void CurlInputStream::InitEasy() { - request = new CurlRequest(*curl_global, GetURI(), *this); + request = new CurlRequest(**curl_init, GetURI(), *this); request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases); request->SetOption(CURLOPT_FOLLOWLOCATION, 1l); @@ -425,7 +416,7 @@ CurlInputStream::DoSeek(offset_type new_offset) inline InputStream * CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond) { - CurlInputStream *c = new CurlInputStream(curl_global->GetEventLoop(), + CurlInputStream *c = new CurlInputStream((*curl_init)->GetEventLoop(), url, mutex, cond); try { diff --git a/src/lib/curl/Init.cxx b/src/lib/curl/Init.cxx new file mode 100644 index 000000000..fca5bafa8 --- /dev/null +++ b/src/lib/curl/Init.cxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Init.hxx" +#include "Global.hxx" +#include "event/Call.hxx" +#include "thread/Mutex.hxx" + +#include + +#include + +Mutex CurlInit::mutex; +unsigned CurlInit::ref; +CurlGlobal *CurlInit::instance; + +CurlInit::CurlInit(EventLoop &event_loop) +{ + const std::lock_guard protect(mutex); + if (++ref > 1) { + assert(&event_loop == &instance->GetEventLoop()); + return; + } + + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code != CURLE_OK) + throw std::runtime_error(curl_easy_strerror(code)); + + assert(instance == nullptr); + instance = new CurlGlobal(event_loop); +} + +CurlInit::~CurlInit() +{ + const std::lock_guard protect(mutex); + if (--ref > 0) + return; + + BlockingCall(instance->GetEventLoop(), [](){ + delete instance; + instance = nullptr; + }); + + curl_global_cleanup(); +} diff --git a/src/lib/curl/Init.hxx b/src/lib/curl/Init.hxx new file mode 100644 index 000000000..b32e27065 --- /dev/null +++ b/src/lib/curl/Init.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CURL_INIT_HXX +#define CURL_INIT_HXX + +#include "check.h" + +class Mutex; +class EventLoop; +class CurlGlobal; + +/** + * This class performs one-time initialization of libCURL and creates + * one #CurlGlobal instance, shared across all #CurlInit instances. + */ +class CurlInit { + static Mutex mutex; + static unsigned ref; + static CurlGlobal *instance; + +public: + explicit CurlInit(EventLoop &event_loop); + ~CurlInit(); + + CurlInit(const CurlInit &) = delete; + CurlInit &operator=(const CurlInit &) = delete; + + CurlGlobal &operator*() { + return *instance; + } + + CurlGlobal *operator->() { + return instance; + } +}; + +#endif diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx index 0d4505a4a..ff660514a 100644 --- a/src/storage/plugins/CurlStorage.cxx +++ b/src/storage/plugins/CurlStorage.cxx @@ -23,6 +23,7 @@ #include "storage/StorageInterface.hxx" #include "storage/FileInfo.hxx" #include "storage/MemoryDirectoryReader.hxx" +#include "lib/curl/Init.hxx" #include "lib/curl/Global.hxx" #include "lib/curl/Slist.hxx" #include "lib/curl/Request.hxx" @@ -49,16 +50,12 @@ class CurlStorage final : public Storage { const std::string base; - CurlGlobal *const curl; + CurlInit curl; public: CurlStorage(EventLoop &_loop, const char *_base) :base(_base), - curl(new CurlGlobal(_loop)) {} - - ~CurlStorage() { - BlockingCall(curl->GetEventLoop(), [this](){ delete curl; }); - } + curl(_loop) {} /* virtual methods from class Storage */ StorageFileInfo GetInfo(const char *uri_utf8, bool follow) override;