summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2024-11-11 15:58:28 +0100
committerMorten Sørvig <morten.sorvig@qt.io>2024-12-02 19:45:18 +0100
commit2e334c139590ff905b2292db87db794876a0583e (patch)
tree5cb343e196f8c13d76ff47ddd71722a08ea1d321 /src
parent9a2370e434987bc008edebe423947cd6d0f38860 (diff)
wasm: move socket notifier impl to separate file
This is not core event dispatcher functionality, and can live in separate implementation files. Change-Id: I56202d59f57e8dd221f39b3b2ed34d57aacb89aa Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/CMakeLists.txt1
-rw-r--r--src/corelib/kernel/qeventdispatcher_wasm.cpp288
-rw-r--r--src/corelib/kernel/qeventdispatcher_wasm_p.h33
-rw-r--r--src/corelib/platform/wasm/qwasmsocket.cpp261
-rw-r--r--src/corelib/platform/wasm/qwasmsocket_p.h56
5 files changed, 351 insertions, 288 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index cc9937a1f87..0db0d58859f 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -1427,6 +1427,7 @@ endif()
qt_internal_extend_target(Core CONDITION WASM
SOURCES
platform/wasm/qstdweb.cpp platform/wasm/qstdweb_p.h
+ platform/wasm/qwasmsocket.cpp platform/wasm/qwasmsocket_p.h
kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h
)
diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp
index b6d1711fe2e..f2ee46ce539 100644
--- a/src/corelib/kernel/qeventdispatcher_wasm.cpp
+++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp
@@ -6,9 +6,8 @@
#include <QtCore/private/qabstracteventdispatcher_p.h> // for qGlobalPostedEventsCount()
#include <QtCore/qcoreapplication.h>
#include <QtCore/qthread.h>
-#include <QtCore/qsocketnotifier.h>
#include <QtCore/private/qstdweb_p.h>
-#include <sys/ioctl.h>
+#include <QtCore/private/qwasmsocket_p.h>
#include "emscripten.h"
#include <emscripten/html5.h>
@@ -124,9 +123,6 @@ Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
emscripten::ProxyingQueue QEventDispatcherWasm::g_proxyingQueue;
pthread_t QEventDispatcherWasm::g_mainThread;
#endif
-// ### dynamic initialization:
-std::multimap<int, QSocketNotifier *> QEventDispatcherWasm::g_socketNotifiers;
-std::map<int, QEventDispatcherWasm::SocketReadyState> QEventDispatcherWasm::g_socketState;
QEventDispatcherWasm::QEventDispatcherWasm()
{
@@ -182,19 +178,8 @@ QEventDispatcherWasm::~QEventDispatcherWasm()
{
if (m_timerId > 0)
emscripten_clear_timeout(m_timerId);
- if (!g_socketNotifiers.empty()) {
- qWarning("QEventDispatcherWasm: main thread event dispatcher deleted with active socket notifiers");
- clearEmscriptenSocketCallbacks();
- g_socketNotifiers.clear();
- }
+ QWasmSocket::clearSocketNotifiers();
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();
}
}
@@ -264,40 +249,6 @@ bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
return false;
}
-void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier)
-{
- LOCK_GUARD(g_staticDataMutex);
-
- bool wasEmpty = g_socketNotifiers.empty();
- g_socketNotifiers.insert({notifier->socket(), notifier});
- if (wasEmpty)
- runOnMainThread([] { setEmscriptenSocketCallbacks(); });
-
- int count;
- ioctl(notifier->socket(), FIONREAD, &count);
-
- // message may have arrived already
- if (count > 0 && notifier->type() == QSocketNotifier::Read) {
- QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct));
- }
-}
-
-void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier)
-{
- LOCK_GUARD(g_staticDataMutex);
-
- auto notifiers = g_socketNotifiers.equal_range(notifier->socket());
- for (auto it = notifiers.first; it != notifiers.second; ++it) {
- if (it->second == notifier) {
- g_socketNotifiers.erase(it);
- break;
- }
- }
-
- if (g_socketNotifiers.empty())
- runOnMainThread([] { clearEmscriptenSocketCallbacks(); });
-}
-
void QEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object)
{
#ifndef QT_NO_DEBUG
@@ -419,6 +370,32 @@ void QEventDispatcherWasm::handleApplicationExec()
}, 0, simulateInfiniteLoop);
}
+void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier)
+{
+ QWasmSocket::registerSocketNotifier(notifier);
+}
+
+void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier)
+{
+ QWasmSocket::unregisterSocketNotifier(notifier);
+}
+
+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;
+ }
+
+ QWasmSocket::waitForSocketState(eventDispatcher, timeout, socket, waitForRead, waitForWrite,
+ selectForRead, selectForWrite, socketDisconnect);
+}
+
+
void QEventDispatcherWasm::handleDialogExec()
{
if (!useAsyncify()) {
@@ -617,215 +594,6 @@ void QEventDispatcherWasm::callProcessTimers(void *context)
#endif
}
-void QEventDispatcherWasm::setEmscriptenSocketCallbacks()
-{
- qCDebug(lcEventDispatcher) << "setEmscriptenSocketCallbacks";
-
- emscripten_set_socket_error_callback(nullptr, QEventDispatcherWasm::socketError);
- emscripten_set_socket_open_callback(nullptr, QEventDispatcherWasm::socketOpen);
- emscripten_set_socket_listen_callback(nullptr, QEventDispatcherWasm::socketListen);
- emscripten_set_socket_connection_callback(nullptr, QEventDispatcherWasm::socketConnection);
- emscripten_set_socket_message_callback(nullptr, QEventDispatcherWasm::socketMessage);
- emscripten_set_socket_close_callback(nullptr, QEventDispatcherWasm::socketClose);
-}
-
-void QEventDispatcherWasm::clearEmscriptenSocketCallbacks()
-{
- qCDebug(lcEventDispatcher) << "clearEmscriptenSocketCallbacks";
-
- emscripten_set_socket_error_callback(nullptr, nullptr);
- emscripten_set_socket_open_callback(nullptr, nullptr);
- emscripten_set_socket_listen_callback(nullptr, nullptr);
- emscripten_set_socket_connection_callback(nullptr, nullptr);
- emscripten_set_socket_message_callback(nullptr, nullptr);
- emscripten_set_socket_close_callback(nullptr, nullptr);
-}
-
-void QEventDispatcherWasm::socketError(int socket, int err, const char* msg, void *context)
-{
- Q_UNUSED(err);
- Q_UNUSED(msg);
- Q_UNUSED(context);
-
- // 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 running 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);
-
- 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);
-}
-
-void QEventDispatcherWasm::socketConnection(int socket, void *context)
-{
- Q_UNUSED(socket);
- Q_UNUSED(context);
-}
-
-void QEventDispatcherWasm::socketMessage(int socket, void *context)
-{
- Q_UNUSED(context);
-
- 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);
-
- // 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 readiness. 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;
-
- 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 {
int g_startupTasks = 0;
}
diff --git a/src/corelib/kernel/qeventdispatcher_wasm_p.h b/src/corelib/kernel/qeventdispatcher_wasm_p.h
index 7b257e02ad1..76abcae69d2 100644
--- a/src/corelib/kernel/qeventdispatcher_wasm_p.h
+++ b/src/corelib/kernel/qeventdispatcher_wasm_p.h
@@ -41,9 +41,6 @@ public:
bool processEvents(QEventLoop::ProcessEventsFlags flags) override;
- void registerSocketNotifier(QSocketNotifier *notifier) override;
- void unregisterSocketNotifier(QSocketNotifier *notifier) override;
-
void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType,
QObject *object) override final;
bool unregisterTimer(Qt::TimerId timerId) override final;
@@ -54,10 +51,13 @@ public:
void interrupt() override;
void wakeUp() override;
- static void runOnMainThread(std::function<void(void)> fn);
+ void registerSocketNotifier(QSocketNotifier *notifier) override;
+ void unregisterSocketNotifier(QSocketNotifier *notifier) override;
static void socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite,
bool *selectForRead, bool *selectForWrite, bool *socketDisconnect);
+ static void runOnMainThread(std::function<void(void)> fn);
+
static void registerStartupTask();
static void completeStarupTask();
static void callOnLoadedIfRequired();
@@ -81,20 +81,6 @@ private:
void updateNativeTimer();
static void callProcessTimers(void *eventDispatcher);
- 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);
- static void socketConnection(int fd, void *context);
- 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 runOnMainThreadAsync(std::function<void(void)> fn);
@@ -124,16 +110,7 @@ private:
// that eventdispatcher thread. The locking order is g_staticDataMutex first, then m_mutex.
#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;
+ friend class QWasmSocket;
};
#endif // QEVENTDISPATCHER_WASM_P_H
diff --git a/src/corelib/platform/wasm/qwasmsocket.cpp b/src/corelib/platform/wasm/qwasmsocket.cpp
new file mode 100644
index 00000000000..e7901a54fb9
--- /dev/null
+++ b/src/corelib/platform/wasm/qwasmsocket.cpp
@@ -0,0 +1,261 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwasmsocket_p.h"
+#include <QtCore/qsocketnotifier.h>
+
+#include "emscripten.h"
+#include <sys/ioctl.h>
+
+#if QT_CONFIG(thread)
+#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)
+#else
+#define LOCK_GUARD(M)
+#endif
+
+#if QT_CONFIG(thread)
+Q_CONSTINIT std::mutex QWasmSocket::g_socketDataMutex;
+#endif
+
+// ### dynamic initialization:
+std::multimap<int, QSocketNotifier *> QWasmSocket::g_socketNotifiers;
+std::map<int, QWasmSocket::SocketReadyState> QWasmSocket::g_socketState;
+
+void QWasmSocket::registerSocketNotifier(QSocketNotifier *notifier)
+{
+ LOCK_GUARD(g_socketDataMutex);
+
+ bool wasEmpty = g_socketNotifiers.empty();
+ g_socketNotifiers.insert({notifier->socket(), notifier});
+ if (wasEmpty)
+ QEventDispatcherWasm::runOnMainThread([] { setEmscriptenSocketCallbacks(); });
+
+ int count;
+ ioctl(notifier->socket(), FIONREAD, &count);
+
+ // message may have arrived already
+ if (count > 0 && notifier->type() == QSocketNotifier::Read) {
+ QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct));
+ }
+}
+
+void QWasmSocket::unregisterSocketNotifier(QSocketNotifier *notifier)
+{
+ LOCK_GUARD(g_socketDataMutex);
+
+ auto notifiers = g_socketNotifiers.equal_range(notifier->socket());
+ for (auto it = notifiers.first; it != notifiers.second; ++it) {
+ if (it->second == notifier) {
+ g_socketNotifiers.erase(it);
+ break;
+ }
+ }
+
+ if (g_socketNotifiers.empty())
+ QEventDispatcherWasm::runOnMainThread([] { clearEmscriptenSocketCallbacks(); });
+}
+
+void QWasmSocket::clearSocketNotifiers()
+{
+ LOCK_GUARD(g_socketDataMutex);
+ if (!g_socketNotifiers.empty()) {
+ qWarning("QWasmSocket: Sockets cleared with active socket notifiers");
+ clearEmscriptenSocketCallbacks();
+ g_socketNotifiers.clear();
+ }
+ g_socketState.clear();
+}
+
+void QWasmSocket::setEmscriptenSocketCallbacks()
+{
+ qCDebug(lcEventDispatcher) << "setEmscriptenSocketCallbacks";
+
+ emscripten_set_socket_error_callback(nullptr, QWasmSocket::socketError);
+ emscripten_set_socket_open_callback(nullptr, QWasmSocket::socketOpen);
+ emscripten_set_socket_listen_callback(nullptr, QWasmSocket::socketListen);
+ emscripten_set_socket_connection_callback(nullptr, QWasmSocket::socketConnection);
+ emscripten_set_socket_message_callback(nullptr, QWasmSocket::socketMessage);
+ emscripten_set_socket_close_callback(nullptr, QWasmSocket::socketClose);
+}
+
+void QWasmSocket::clearEmscriptenSocketCallbacks()
+{
+ qCDebug(lcEventDispatcher) << "clearEmscriptenSocketCallbacks";
+
+ emscripten_set_socket_error_callback(nullptr, nullptr);
+ emscripten_set_socket_open_callback(nullptr, nullptr);
+ emscripten_set_socket_listen_callback(nullptr, nullptr);
+ emscripten_set_socket_connection_callback(nullptr, nullptr);
+ emscripten_set_socket_message_callback(nullptr, nullptr);
+ emscripten_set_socket_close_callback(nullptr, nullptr);
+}
+
+void QWasmSocket::socketError(int socket, int err, const char* msg, void *context)
+{
+ Q_UNUSED(err);
+ Q_UNUSED(msg);
+ Q_UNUSED(context);
+
+ // 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 running 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.
+ QEventDispatcherWasm::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 QWasmSocket::socketOpen(int socket, void *context)
+{
+ Q_UNUSED(context);
+
+ QEventDispatcherWasm::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 QWasmSocket::socketListen(int socket, void *context)
+{
+ Q_UNUSED(socket);
+ Q_UNUSED(context);
+}
+
+void QWasmSocket::socketConnection(int socket, void *context)
+{
+ Q_UNUSED(socket);
+ Q_UNUSED(context);
+}
+
+void QWasmSocket::socketMessage(int socket, void *context)
+{
+ Q_UNUSED(context);
+
+ QEventDispatcherWasm::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 QWasmSocket::socketClose(int socket, void *context)
+{
+ Q_UNUSED(context);
+
+ // 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;
+
+ QEventDispatcherWasm::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 QWasmSocket::setSocketState(int socket, bool setReadyRead, bool setReadyWrite)
+{
+ LOCK_GUARD(g_socketDataMutex);
+ 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 readiness. 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->wakeUp();
+}
+
+void QWasmSocket::clearSocketState(int socket)
+{
+ LOCK_GUARD(g_socketDataMutex);
+ g_socketState.erase(socket);
+}
+
+void QWasmSocket::waitForSocketState(QEventDispatcherWasm *eventDispatcher, 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_socketDataMutex);
+
+ // 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;
+
+ state.waiter = eventDispatcher;
+ state.waitForReadyRead = checkRead;
+ state.waitForReadyWrite = checkWrite;
+ }
+
+ bool didTimeOut = !eventDispatcher->wait(timeout);
+ {
+ LOCK_GUARD(g_socketDataMutex);
+
+ // 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;
+ }
+}
diff --git a/src/corelib/platform/wasm/qwasmsocket_p.h b/src/corelib/platform/wasm/qwasmsocket_p.h
new file mode 100644
index 00000000000..da2ef94cdf0
--- /dev/null
+++ b/src/corelib/platform/wasm/qwasmsocket_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWASMSOCKET_P_H
+#define QWASMSOCKET_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtCore/private/qeventdispatcher_wasm_p.h"
+
+class QWasmSocket
+{
+public:
+ static void registerSocketNotifier(QSocketNotifier *notifier);
+ static void unregisterSocketNotifier(QSocketNotifier *notifier);
+ static void clearSocketNotifiers();
+
+ 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);
+ static void socketConnection(int fd, void *context);
+ 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);
+ static void waitForSocketState(QEventDispatcherWasm *eventDispatcher, int timeout, int socket,
+ bool checkRead, bool checkWrite, bool *selectForRead, bool *selectForWrite, bool *socketDisconnect);
+private:
+
+#if QT_CONFIG(thread)
+ Q_CONSTINIT static std::mutex g_socketDataMutex;
+#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 // QWASMSOCKET_P_H