diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2017-07-19 23:32:30 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2017-08-27 16:37:08 +0000 |
commit | db0585d63ad44756c9b9476a53413ccd061c52d8 (patch) | |
tree | cef77ff31b1b8eab4a6103b35958657ef58f6edc /src/network/kernel/qnetworkproxy_libproxy.cpp | |
parent | 0f11fab6f75dec78d3721280971448cc2edd6e72 (diff) |
Fix crashes with libproxy+webkitgtk: it's not threadsafe
I'm getting crashes in Akonadi processes due to libproxy. I don't have
direct evidence that this was caused by a threading condition, but it's
clear from the source code of libproxy that the plugins it runs for
expanding PAC scripts are not thread-safe. To overcome this problem, we
only run libproxy functions in one thread only.
#0 0x00007f745f0ac1d8 in JSC::HeapTimer::timerDidFire() () at /usr/lib64/libjavascriptcoregtk-4.0.so.18
#1 0x00007f745f0ac287 in () at /usr/lib64/libjavascriptcoregtk-4.0.so.18
#2 0x00007f748e5ae9c5 in g_main_context_dispatch () at /usr/lib64/libglib-2.0.so.0
#3 0x00007f748e5aed88 in () at /usr/lib64/libglib-2.0.so.0
#4 0x00007f748e5aee1c in g_main_context_iteration () at /usr/lib64/libglib-2.0.so.0
#5 0x00007f7494f4268f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /usr/lib64/libQt5Core.so.5
#6 0x00007f7494eeb35a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () at /usr/lib64/libQt5Core.so.5
#7 0x00007f7494d1b31a in QThread::exec() () at /usr/lib64/libQt5Core.so.5
#8 0x00007f7494d1fd2e in () at /usr/lib64/libQt5Core.so.5
#9 0x00007f74913174e7 in start_thread () at /lib64/libpthread.so.0
The pacrunner implementation of libproxy uses libdbus-1 which
(officially) is thread-safe, but experience tells that it has
problems. Since it is not running a JS engine, we don't need a thread,
but we do need to lock around it.
Change-Id: I84e45059a888497fb55ffffd14d2f638f21e807d
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network/kernel/qnetworkproxy_libproxy.cpp')
-rw-r--r-- | src/network/kernel/qnetworkproxy_libproxy.cpp | 143 |
1 files changed, 119 insertions, 24 deletions
diff --git a/src/network/kernel/qnetworkproxy_libproxy.cpp b/src/network/kernel/qnetworkproxy_libproxy.cpp index 184dc6469d..29d2a0bd3b 100644 --- a/src/network/kernel/qnetworkproxy_libproxy.cpp +++ b/src/network/kernel/qnetworkproxy_libproxy.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -42,57 +43,149 @@ #ifndef QT_NO_NETWORKPROXY #include <QtCore/QByteArray> +#include <QtCore/QMutex> +#include <QtCore/QSemaphore> #include <QtCore/QUrl> +#include <QtCore/private/qeventdispatcher_unix_p.h> +#include <QtCore/private/qthread_p.h> #include <proxy.h> +#include <dlfcn.h> QT_BEGIN_NAMESPACE -class QLibProxyWrapper +static bool isThreadingNeeded() { -public: - QLibProxyWrapper() - : factory(px_proxy_factory_new()) - { - if (!factory) - qWarning("libproxy initialization failed."); - } + // Try to guess if the libproxy we linked to is from the libproxy project + // or if it is from pacrunner. Neither library is thread-safe, but the one + // from libproxy is worse, since it may launch JS engines that don't take + // kindly to being executed from multiple threads (even if at different + // times). The pacrunner implementation doesn't suffer from this because + // the JS execution is out of process, in the pacrunner daemon. - ~QLibProxyWrapper() - { - px_proxy_factory_free(factory); - } + void *sym; + +#ifdef Q_CC_GNU + // Search for the mangled name of the virtual table of the pacrunner + // extension. Even if libproxy begins using -fvisibility=hidden, this + // symbol can't be hidden. + sym = dlsym(RTLD_DEFAULT, "_ZTVN8libproxy19pacrunner_extensionE"); +#else + // The default libproxy one uses libmodman for its module management and + // leaks symbols because it doesn't use -fvisibility=hidden (as of + // v0.4.15). + sym = dlsym(RTLD_DEFAULT, "mm_info_ignore_hostname"); +#endif + + return sym != nullptr; +} + +class QLibProxyWrapper : public QDaemonThread +{ + Q_OBJECT +public: + QLibProxyWrapper(); + ~QLibProxyWrapper(); QList<QUrl> getProxies(const QUrl &url); private: - pxProxyFactory *factory; + struct Data { + // we leave the conversion to/from QUrl to the calling thread + const char *url; + char **proxies; + QSemaphore replyReady; + }; + + void run() override; + + pxProxyFactory *factory; // not subject to the mutex + + QMutex mutex; + QSemaphore requestReady; + Data *request; }; Q_GLOBAL_STATIC(QLibProxyWrapper, libProxyWrapper); +QLibProxyWrapper::QLibProxyWrapper() +{ + if (isThreadingNeeded()) { + setEventDispatcher(new QEventDispatcherUNIX); // don't allow the Glib one + start(); + } else { + factory = px_proxy_factory_new(); + Q_CHECK_PTR(factory); + } +} + +QLibProxyWrapper::~QLibProxyWrapper() +{ + if (isRunning()) { + requestInterruption(); + requestReady.release(); + wait(); + } else { + px_proxy_factory_free(factory); + } +} + /* - Gets the list of proxies from libproxy, converted to QUrl list. - Thread safe, according to libproxy documentation. + Gets the list of proxies from libproxy, converted to QUrl list. Apply + thread-safety, though its documentation says otherwise, libproxy isn't + thread-safe. */ QList<QUrl> QLibProxyWrapper::getProxies(const QUrl &url) { - QList<QUrl> ret; + QByteArray encodedUrl = url.toEncoded(); + Data data; + data.url = encodedUrl.constData(); + + { + QMutexLocker locker(&mutex); + if (isRunning()) { + // threaded mode + // it's safe to write to request because we hold the mutex: + // our aux thread is blocked waiting for work and no other thread + // could have got here + request = &data; + requestReady.release(); - if (factory) { - char **proxies = px_proxy_factory_get_proxies(factory, url.toEncoded()); - if (proxies) { - for (int i = 0; proxies[i]; i++) { - ret.append(QUrl::fromEncoded(proxies[i])); - free(proxies[i]); - } - free(proxies); + // wait for the reply + data.replyReady.acquire(); + } else { + // non-threaded mode + data.proxies = px_proxy_factory_get_proxies(factory, data.url); } } + QList<QUrl> ret; + if (data.proxies) { + for (int i = 0; data.proxies[i]; i++) { + ret.append(QUrl::fromEncoded(data.proxies[i])); + free(data.proxies[i]); + } + free(data.proxies); + } return ret; } +void QLibProxyWrapper::run() +{ + factory = px_proxy_factory_new(); + Q_CHECK_PTR(factory); + + forever { + requestReady.acquire(); + if (isInterruptionRequested()) + break; + request->proxies = px_proxy_factory_get_proxies(factory, request->url); + request->replyReady.release(); + } + + px_proxy_factory_free(factory); +} + QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) { QList<QNetworkProxy> proxyList; @@ -161,4 +254,6 @@ QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro QT_END_NAMESPACE +#include "qnetworkproxy_libproxy.moc" + #endif |