Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmsocket.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qsocketnotifier.h>
9
10#include "emscripten.h"
11#include <sys/ioctl.h>
12
13#if QT_CONFIG(thread)
14#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)
15#else
16#define LOCK_GUARD(M)
17#endif
18
19#if QT_CONFIG(thread)
20Q_CONSTINIT std::mutex QWasmSocket::g_socketDataMutex;
21#endif
22
23// ### dynamic initialization:
24std::multimap<int, QSocketNotifier *> QWasmSocket::g_socketNotifiers;
25std::map<int, QWasmSocket::SocketReadyState> QWasmSocket::g_socketState;
26
27void QWasmSocket::registerSocketNotifier(QSocketNotifier *notifier)
28{
29 LOCK_GUARD(g_socketDataMutex);
30
31 bool wasEmpty = g_socketNotifiers.empty();
32 g_socketNotifiers.insert({notifier->socket(), notifier});
33 if (wasEmpty)
35
36 int count;
37 ioctl(notifier->socket(), FIONREAD, &count);
38
39 // message may have arrived already
40 if (count > 0 && notifier->type() == QSocketNotifier::Read) {
41 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct));
42 }
43}
44
45void QWasmSocket::unregisterSocketNotifier(QSocketNotifier *notifier)
46{
47 LOCK_GUARD(g_socketDataMutex);
48
49 auto notifiers = g_socketNotifiers.equal_range(notifier->socket());
50 for (auto it = notifiers.first; it != notifiers.second; ++it) {
51 if (it->second == notifier) {
52 g_socketNotifiers.erase(it);
53 break;
54 }
55 }
56
57 if (g_socketNotifiers.empty())
59}
60
62{
63 LOCK_GUARD(g_socketDataMutex);
64 if (!g_socketNotifiers.empty()) {
65 qWarning("QWasmSocket: Sockets cleared with active socket notifiers");
67 g_socketNotifiers.clear();
68 }
69 g_socketState.clear();
70}
71
73{
74 qCDebug(lcEventDispatcher) << "setEmscriptenSocketCallbacks";
75
76 emscripten_set_socket_error_callback(nullptr, QWasmSocket::socketError);
77 emscripten_set_socket_open_callback(nullptr, QWasmSocket::socketOpen);
78 emscripten_set_socket_listen_callback(nullptr, QWasmSocket::socketListen);
79 emscripten_set_socket_connection_callback(nullptr, QWasmSocket::socketConnection);
80 emscripten_set_socket_message_callback(nullptr, QWasmSocket::socketMessage);
81 emscripten_set_socket_close_callback(nullptr, QWasmSocket::socketClose);
82}
83
85{
86 qCDebug(lcEventDispatcher) << "clearEmscriptenSocketCallbacks";
87
88 emscripten_set_socket_error_callback(nullptr, nullptr);
89 emscripten_set_socket_open_callback(nullptr, nullptr);
90 emscripten_set_socket_listen_callback(nullptr, nullptr);
91 emscripten_set_socket_connection_callback(nullptr, nullptr);
92 emscripten_set_socket_message_callback(nullptr, nullptr);
93 emscripten_set_socket_close_callback(nullptr, nullptr);
94}
95
96void QWasmSocket::socketError(int socket, int err, const char* msg, void *context)
97{
98 Q_UNUSED(err);
99 Q_UNUSED(msg);
100 Q_UNUSED(context);
101
102 // Emscripten makes socket callbacks while the main thread is busy-waiting for a mutex,
103 // which can cause deadlocks if the callback code also tries to lock the same mutex.
104 // This is most easily reproducible by adding print statements, where each print requires
105 // taking a mutex lock. Work around this by running the callback asynchronously, i.e. by using
106 // a native zero-timer, to make sure the main thread stack is completely unwond before calling
107 // the Qt handler.
108 // It is currently unclear if this problem is caused by code in Qt or in Emscripten, or
109 // if this completely fixes the problem.
110 qwasmglobal::runAsync([socket](){
111 auto notifiersRange = g_socketNotifiers.equal_range(socket);
112 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
113 for (auto [_, notifier]: notifiers) {
114 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct));
115 }
116 setSocketState(socket, true, true);
117 });
118}
119
120void QWasmSocket::socketOpen(int socket, void *context)
121{
122 Q_UNUSED(context);
123
124 qwasmglobal::runAsync([socket](){
125 auto notifiersRange = g_socketNotifiers.equal_range(socket);
126 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
127 for (auto [_, notifier]: notifiers) {
128 if (notifier->type() == QSocketNotifier::Write) {
129 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct));
130 }
131 }
132 setSocketState(socket, false, true);
133 });
134}
135
136void QWasmSocket::socketListen(int socket, void *context)
137{
138 Q_UNUSED(socket);
139 Q_UNUSED(context);
140}
141
142void QWasmSocket::socketConnection(int socket, void *context)
143{
144 Q_UNUSED(socket);
145 Q_UNUSED(context);
146}
147
148void QWasmSocket::socketMessage(int socket, void *context)
149{
150 Q_UNUSED(context);
151
152 qwasmglobal::runAsync([socket](){
153 auto notifiersRange = g_socketNotifiers.equal_range(socket);
154 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
155 for (auto [_, notifier]: notifiers) {
156 if (notifier->type() == QSocketNotifier::Read) {
157 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockAct));
158 }
159 }
160 setSocketState(socket, true, false);
161 });
162}
163
164void QWasmSocket::socketClose(int socket, void *context)
165{
166 Q_UNUSED(context);
167
168 // Emscripten makes emscripten_set_socket_close_callback() calls to socket 0,
169 // which is not a valid socket. see https://github.com/emscripten-core/emscripten/issues/6596
170 if (socket == 0)
171 return;
172
173 qwasmglobal::runAsync([socket](){
174 auto notifiersRange = g_socketNotifiers.equal_range(socket);
175 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
176 for (auto [_, notifier]: notifiers)
177 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockClose));
178
179 setSocketState(socket, true, true);
180 clearSocketState(socket);
181 });
182}
183
184void QWasmSocket::setSocketState(int socket, bool setReadyRead, bool setReadyWrite)
185{
186 LOCK_GUARD(g_socketDataMutex);
187 SocketReadyState &state = g_socketState[socket];
188
189 // Additively update socket ready state, e.g. if it
190 // was already ready read then it stays ready read.
191 state.readyRead |= setReadyRead;
192 state.readyWrite |= setReadyWrite;
193
194 // Wake any waiters for the given readiness. The waiter consumes
195 // the ready state, returning the socket to not-ready.
196 if (QEventDispatcherWasm *waiter = state.waiter)
197 if ((state.readyRead && state.waitForReadyRead) || (state.readyWrite && state.waitForReadyWrite))
198 waiter->wakeUp();
199}
200
202{
203 LOCK_GUARD(g_socketDataMutex);
204 g_socketState.erase(socket);
205}
206
207void QWasmSocket::waitForSocketState(QEventDispatcherWasm *eventDispatcher, int timeout, int socket, bool checkRead,
208 bool checkWrite, bool *selectForRead, bool *selectForWrite, bool *socketDisconnect)
209{
210 // Loop until the socket becomes readyRead or readyWrite. Wait for
211 // socket activity if it currently is neither.
212 while (true) {
213 *selectForRead = false;
214 *selectForWrite = false;
215
216 {
217 LOCK_GUARD(g_socketDataMutex);
218
219 // Access or create socket state: we want to register that a thread is waitng
220 // even if we have not received any socket callbacks yet.
221 SocketReadyState &state = g_socketState[socket];
222 if (state.waiter) {
223 qWarning() << "QEventDispatcherWasm::waitForSocketState: a thread is already waiting";
224 break;
225 }
226
227 bool shouldWait = true;
228 if (checkRead && state.readyRead) {
229 shouldWait = false;
230 state.readyRead = false;
231 *selectForRead = true;
232 }
233 if (checkWrite && state.readyWrite) {
234 shouldWait = false;
235 state.readyWrite = false;
236 *selectForRead = true;
237 }
238 if (!shouldWait)
239 break;
240
241 state.waiter = eventDispatcher;
242 state.waitForReadyRead = checkRead;
243 state.waitForReadyWrite = checkWrite;
244 }
245
246 bool didTimeOut = !eventDispatcher->wait(timeout);
247 {
248 LOCK_GUARD(g_socketDataMutex);
249
250 // Missing socket state after a wakeup means that the socket has been closed.
251 auto it = g_socketState.find(socket);
252 if (it == g_socketState.end()) {
253 *socketDisconnect = true;
254 break;
255 }
256 it->second.waiter = nullptr;
257 it->second.waitForReadyRead = false;
258 it->second.waitForReadyWrite = false;
259 }
260
261 if (didTimeOut)
262 break;
263 }
264}
static void setSocketState(int socket, bool setReadyRead, bool setReadyWrite)
static void setEmscriptenSocketCallbacks()
static void socketConnection(int fd, void *context)
static void socketClose(int fd, void *context)
static void clearSocketState(int socket)
static void clearEmscriptenSocketCallbacks()
static void socketListen(int fd, void *context)
static void socketOpen(int fd, void *context)
static void clearSocketNotifiers()
static void socketError(int fd, int err, const char *msg, void *context)
static void socketMessage(int fd, void *context)
void runAsync(std::function< void(void)> fn)
void runOnMainThread(std::function< void(void)> fn)
#define LOCK_GUARD(M)