diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/kernel/qeventdispatcher_wasm.cpp | 212 | ||||
-rw-r--r-- | src/corelib/kernel/qeventdispatcher_wasm_p.h | 22 | ||||
-rw-r--r-- | src/network/socket/qnativesocketengine_unix.cpp | 25 |
3 files changed, 205 insertions, 54 deletions
diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp index 0fec7cb7ef2..9cff1eb723d 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm.cpp +++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp @@ -96,6 +96,7 @@ Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex; #endif // ### dynamic initialization: std::multimap<int, QSocketNotifier *> QEventDispatcherWasm::g_socketNotifiers; +std::map<int, QEventDispatcherWasm::SocketReadyState> QEventDispatcherWasm::g_socketState; QEventDispatcherWasm::QEventDispatcherWasm() : QAbstractEventDispatcher() @@ -149,6 +150,13 @@ QEventDispatcherWasm::~QEventDispatcherWasm() g_socketNotifiers.clear(); } g_mainThreadEventDispatcher = nullptr; + if (!g_socketNotifiers.empty()) { + qWarning("QEventDispatcherWasm: main thread event dispatcher deleted with active socket notifiers"); + clearEmscriptenSocketCallbacks(); + g_socketNotifiers.clear(); + } + + g_socketState.clear(); } } @@ -221,23 +229,17 @@ void QEventDispatcherWasm::processWindowSystemEvents(QEventLoop::ProcessEventsFl void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier) { - if (!emscripten_is_main_runtime_thread()) { - qWarning("QEventDispatcherWasm::registerSocketNotifier: socket notifiers on secondary threads are not supported"); - return; - } - - if (g_socketNotifiers.empty()) - setEmscriptenSocketCallbacks(); + LOCK_GUARD(g_staticDataMutex); + bool wasEmpty = g_socketNotifiers.empty(); g_socketNotifiers.insert({notifier->socket(), notifier}); + if (wasEmpty) + runOnMainThread([]{ setEmscriptenSocketCallbacks(); }); } void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier) { - if (!emscripten_is_main_runtime_thread()) { - qWarning("QEventDispatcherWasm::registerSocketNotifier: socket notifiers on secondary threads are not supported"); - return; - } + LOCK_GUARD(g_staticDataMutex); auto notifiers = g_socketNotifiers.equal_range(notifier->socket()); for (auto it = notifiers.first; it != notifiers.second; ++it) { @@ -248,7 +250,7 @@ void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier) } if (g_socketNotifiers.empty()) - clearEmscriptenSocketCallbacks(); + runOnMainThread([]{ clearEmscriptenSocketCallbacks(); }); } void QEventDispatcherWasm::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object) @@ -425,8 +427,11 @@ bool QEventDispatcherWasm::wait(int timeout) qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME bool didSuspend = qt_asyncify_suspend(); - if (!didSuspend) + if (!didSuspend) { qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events"); + return false; + } + return true; #else qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify"); Q_UNUSED(timeout); @@ -605,78 +610,183 @@ void QEventDispatcherWasm::socketError(int socket, int err, const char* msg, voi Q_UNUSED(msg); Q_UNUSED(context); - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::socketError" << socket; - - auto notifiersRange = g_socketNotifiers.equal_range(socket); - std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); - for (auto [_, notifier]: notifiers) { - QEvent event(QEvent::SockAct); - QCoreApplication::sendEvent(notifier, &event); - } + // Emscripten makes socket callbacks while the main thread is busy-waiting for a mutex, + // which can cause deadlocks if the callback code also tries to lock the same mutex. + // This is most easily reproducible by adding print statements, where each print requires + // taking a mutex lock. Work around this by runnign the callback asynchronously, i.e. by using + // a native zero-timer, to make sure the main thread stack is completely unwond before calling + // the Qt handler. + // It is currently unclear if this problem is caused by code in Qt or in Emscripten, or + // if this completely fixes the problem. + runAsync([socket](){ + auto notifiersRange = g_socketNotifiers.equal_range(socket); + std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); + for (auto [_, notifier]: notifiers) { + QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct)); + } + setSocketState(socket, true, true); + }); } void QEventDispatcherWasm::socketOpen(int socket, void *context) { Q_UNUSED(context); - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::socketOpen" << socket; - - auto notifiersRange = g_socketNotifiers.equal_range(socket); - std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); - for (auto [_, notifier]: notifiers) { - if (notifier->type() == QSocketNotifier::Write) { - QEvent event(QEvent::SockAct); - QCoreApplication::sendEvent(notifier, &event); + runAsync([socket](){ + auto notifiersRange = g_socketNotifiers.equal_range(socket); + std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); + for (auto [_, notifier]: notifiers) { + if (notifier->type() == QSocketNotifier::Write) { + QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct)); + } } - } + setSocketState(socket, false, true); + }); } void QEventDispatcherWasm::socketListen(int socket, void *context) { Q_UNUSED(socket); Q_UNUSED(context); - - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::socketListen" << socket; } void QEventDispatcherWasm::socketConnection(int socket, void *context) { - Q_UNUSED(context); Q_UNUSED(socket); - - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::socketConnection" << socket; + Q_UNUSED(context); } void QEventDispatcherWasm::socketMessage(int socket, void *context) { Q_UNUSED(context); - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::socketMessage" << socket; - - auto notifiersRange = g_socketNotifiers.equal_range(socket); - std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); - for (auto [_, notifier]: notifiers) { - if (notifier->type() == QSocketNotifier::Read) { - QEvent event(QEvent::SockAct); - QCoreApplication::sendEvent(notifier, &event); + runAsync([socket](){ + auto notifiersRange = g_socketNotifiers.equal_range(socket); + std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); + for (auto [_, notifier]: notifiers) { + if (notifier->type() == QSocketNotifier::Read) { + QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct)); + } } - } + setSocketState(socket, true, false); + }); } void QEventDispatcherWasm::socketClose(int socket, void *context) { Q_UNUSED(context); - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::socketClose" << socket; + // Emscripten makes emscripten_set_socket_close_callback() calls to socket 0, + // which is not a valid socket. see https://github.com/emscripten-core/emscripten/issues/6596 + if (socket == 0) + return; + + runAsync([socket](){ + auto notifiersRange = g_socketNotifiers.equal_range(socket); + std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); + for (auto [_, notifier]: notifiers) + QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockClose)); + + setSocketState(socket, true, true); + clearSocketState(socket); + }); +} + +void QEventDispatcherWasm::setSocketState(int socket, bool setReadyRead, bool setReadyWrite) +{ + LOCK_GUARD(g_staticDataMutex); + SocketReadyState &state = g_socketState[socket]; + + // Additively update socket ready state, e.g. if it + // was already ready read then it stays ready read. + state.readyRead |= setReadyRead; + state.readyWrite |= setReadyWrite; + + // Wake any waiters for the given readyness. The waiter consumes + // the ready state, returning the socket to not-ready. + if (QEventDispatcherWasm *waiter = state.waiter) + if ((state.readyRead && state.waitForReadyRead) || (state.readyWrite && state.waitForReadyWrite)) + waiter->wakeEventDispatcherThread(); +} + +void QEventDispatcherWasm::clearSocketState(int socket) +{ + LOCK_GUARD(g_staticDataMutex); + g_socketState.erase(socket); +} + +void QEventDispatcherWasm::waitForSocketState(int timeout, int socket, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite, bool *socketDisconnect) +{ + // Loop until the socket becomes readyRead or readyWrite. Wait for + // socket activity if it currently is neither. + while (true) { + *selectForRead = false; + *selectForWrite = false; + + { + LOCK_GUARD(g_staticDataMutex); + + // Access or create socket state: we want to register that a thread is waitng + // even if we have not received any socket callbacks yet. + SocketReadyState &state = g_socketState[socket]; + if (state.waiter) { + qWarning() << "QEventDispatcherWasm::waitForSocketState: a thread is already waiting"; + break; + } + + bool shouldWait = true; + if (checkRead && state.readyRead) { + shouldWait = false; + state.readyRead = false; + *selectForRead = true; + } + if (checkWrite && state.readyWrite) { + shouldWait = false; + state.readyWrite = false; + *selectForRead = true; + } + if (!shouldWait) + break; - auto notifiersRange = g_socketNotifiers.equal_range(socket); - std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second); - for (auto [_, notifier]: notifiers) { - if (notifier->type() == QSocketNotifier::Write) { - QEvent event(QEvent::SockAct); - QCoreApplication::sendEvent(notifier, &event); + state.waiter = this; + state.waitForReadyRead = checkRead; + state.waitForReadyWrite = checkWrite; } + + bool didTimeOut = !wait(timeout); + { + LOCK_GUARD(g_staticDataMutex); + + // Missing socket state after a wakeup means that the socket has been closed. + auto it = g_socketState.find(socket); + if (it == g_socketState.end()) { + *socketDisconnect = true; + break; + } + it->second.waiter = nullptr; + it->second.waitForReadyRead = false; + it->second.waitForReadyWrite = false; + } + + if (didTimeOut) + break; + } +} + +void QEventDispatcherWasm::socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite, + bool *selectForRead, bool *selectForWrite, bool *socketDisconnect) +{ + QEventDispatcherWasm *eventDispatcher = static_cast<QEventDispatcherWasm *>( + QAbstractEventDispatcher::instance(QThread::currentThread())); + + if (!eventDispatcher) { + qWarning("QEventDispatcherWasm::socketSelect called without eventdispatcher instance"); + return; } + + eventDispatcher->waitForSocketState(timeout, socket, waitForRead, waitForWrite, + selectForRead, selectForWrite, socketDisconnect); } namespace { diff --git a/src/corelib/kernel/qeventdispatcher_wasm_p.h b/src/corelib/kernel/qeventdispatcher_wasm_p.h index eea4f813dbf..a0cd182d821 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm_p.h +++ b/src/corelib/kernel/qeventdispatcher_wasm_p.h @@ -50,7 +50,9 @@ public: void interrupt() override; void wakeUp() override; -protected: + static void socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite, + bool *selectForRead, bool *selectForWrite, bool *socketDisconnect); + protected: virtual void processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags); private: @@ -69,8 +71,8 @@ private: void updateNativeTimer(); static void callProcessTimers(void *eventDispatcher); - void setEmscriptenSocketCallbacks(); - void clearEmscriptenSocketCallbacks(); + static void setEmscriptenSocketCallbacks(); + static void clearEmscriptenSocketCallbacks(); static void socketError(int fd, int err, const char* msg, void *context); static void socketOpen(int fd, void *context); static void socketListen(int fd, void *context); @@ -78,6 +80,11 @@ private: static void socketMessage(int fd, void *context); static void socketClose(int fd, void *context); + static void setSocketState(int socket, bool setReadyRead, bool setReadyWrite); + static void clearSocketState(int socket); + void waitForSocketState(int timeout, int socket, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite, bool *socketDisconnect); + static void run(std::function<void(void)> fn); static void runAsync(std::function<void(void)> fn); static void runOnMainThread(std::function<void(void)> fn); @@ -107,6 +114,15 @@ private: #endif static std::multimap<int, QSocketNotifier *> g_socketNotifiers; + + struct SocketReadyState { + QEventDispatcherWasm *waiter = nullptr; + bool waitForReadyRead = false; + bool waitForReadyWrite = false; + bool readyRead = false; + bool readyWrite = false; + }; + static std::map<int, SocketReadyState> g_socketState; }; #endif // QEVENTDISPATCHER_WASM_P_H diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index dfa18849a12..2c2ea706e52 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -11,6 +11,9 @@ #include "qvarlengtharray.h" #include "qnetworkinterface.h" #include "qendian.h" +#ifdef Q_OS_WASM +#include <private/qeventdispatcher_wasm_p.h> +#endif #include <time.h> #include <errno.h> #include <fcntl.h> @@ -1353,6 +1356,8 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) co return nativeSelect(timeout, selectForRead, !selectForRead, &dummy, &dummy); } +#ifndef Q_OS_WASM + int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, bool *selectForRead, bool *selectForWrite) const { @@ -1383,4 +1388,24 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool c return ret; } +#else + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + *selectForRead = checkRead; + *selectForWrite = checkWrite; + bool socketDisconnect = false; + QEventDispatcherWasm::socketSelect(timeout, socketDescriptor, checkRead, checkWrite,selectForRead, selectForWrite, &socketDisconnect); + + // The disconnect/close handling code in QAbstractsScket::canReadNotification() + // does not detect remote disconnect properly; do that here as a workardound. + if (socketDisconnect) + receiver->closeNotification(); + + return 1; +} + +#endif // Q_OS_WASM + QT_END_NAMESPACE |