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