summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2025-08-25 16:00:26 +0200
committerMorten Sørvig <morten.sorvig@qt.io>2025-11-03 15:22:24 +0200
commitb06fc757661ce00da422e099eb8ff7ed51ba65ac (patch)
treea80a8b49c6d49ace96b5e55ad4c215b0d64b4c5c
parent77ccbd61f09c17d47b153bff41dd4a6bf7da0557 (diff)
wasm: implement exclusive suspend mode
This mode supports suspending the instance while waiting for a single event only. All other events are queued for later delivery. This is useful for async calls made behind synchronous API, where we don't want to run general event processing while the instance is waiting for the async call to complete. This is for example the case for file read() type calls. Change-Id: I8a8e8b15fea7a60a6bf069812294447505bdc717 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io> Reviewed-by: Even Oscar Andersen <even.oscar.andersen@qt.io>
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp43
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h2
-rw-r--r--tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp45
-rw-r--r--tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp2
4 files changed, 86 insertions, 6 deletions
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
index 961bbb53e54..466d8712b9b 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
@@ -49,6 +49,7 @@ void qtSuspendResumeControlClearJs() {
asyncifyEnabled: false, // asyncify 1 or JSPI enabled
eventHandlers: {},
pendingEvents: [],
+ exclusiveEventHandler: 0,
});
});
}
@@ -118,7 +119,15 @@ void qtRegisterEventHandlerJs(int index) {
});
// Handle the event based on instance state and asyncify flag
- if (control.resume) {
+ if (control.exclusiveEventHandler > 0) {
+ // In exclusive mode, resume on exclusive event handler match only
+ if (index != control.exclusiveEventHandler)
+ return;
+
+ const resume = control.resume;
+ control.resume = null;
+ resume();
+ } else if (control.resume) {
// The instance is suspended in processEvents(), resume and process the event
const resume = control.resume;
control.resume = null;
@@ -148,14 +157,12 @@ QWasmSuspendResumeControl::QWasmSuspendResumeControl()
#endif
qtSuspendResumeControlClearJs();
suspendResumeControlJs().set("asyncifyEnabled", qstdweb::haveAsyncify());
- Q_ASSERT(!QWasmSuspendResumeControl::s_suspendResumeControl);
QWasmSuspendResumeControl::s_suspendResumeControl = this;
}
QWasmSuspendResumeControl::~QWasmSuspendResumeControl()
{
qtSuspendResumeControlClearJs();
- Q_ASSERT(QWasmSuspendResumeControl::s_suspendResumeControl);
QWasmSuspendResumeControl::s_suspendResumeControl = nullptr;
}
@@ -199,13 +206,24 @@ void QWasmSuspendResumeControl::suspend()
qtSuspendJs();
}
-// Sends any pending events. Returns true if an event was sent, false otherwise.
+void QWasmSuspendResumeControl::suspendExclusive(uint32_t eventHandlerIndex)
+{
+ suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndex);
+ qtSuspendJs();
+}
+
+// Sends any pending events. Returns the number of sent events.
int QWasmSuspendResumeControl::sendPendingEvents()
{
#if QT_CONFIG(thread)
Q_ASSERT(emscripten_is_main_runtime_thread());
#endif
- emscripten::val pendingEvents = suspendResumeControlJs()["pendingEvents"];
+ emscripten::val control = suspendResumeControlJs();
+ emscripten::val pendingEvents = control["pendingEvents"];
+
+ if (control["exclusiveEventHandler"].as<int>() > 0)
+ return sendPendingExclusiveEvent();
+
if (pendingEvents["length"].as<int>() == 0)
return 0;
@@ -221,6 +239,21 @@ int QWasmSuspendResumeControl::sendPendingEvents()
return count;
}
+// Sends the pending exclusive event, and resets the "exclusive" state
+int QWasmSuspendResumeControl::sendPendingExclusiveEvent()
+{
+ emscripten::val control = suspendResumeControlJs();
+ int exclusiveHandlerIndex = control["exclusiveEventHandler"].as<int>();
+ control.set("exclusiveEventHandler", 0);
+ emscripten::val event = control["pendingEvents"].call<val>("pop");
+ int eventHandlerIndex = event["index"].as<int>();
+ Q_ASSERT(exclusiveHandlerIndex == eventHandlerIndex);
+ auto it = m_eventHandlers.find(eventHandlerIndex);
+ Q_ASSERT(it != m_eventHandlers.end());
+ it->second(event["arg"]);
+ return 1;
+}
+
void qtSendPendingEvents()
{
if (QWasmSuspendResumeControl::s_suspendResumeControl)
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
index 2b9962e4be1..37c71ed8123 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
@@ -38,7 +38,9 @@ public:
static emscripten::val suspendResumeControlJs();
void suspend();
+ void suspendExclusive(uint32_t eventHandlerIndex);
int sendPendingEvents();
+ int sendPendingExclusiveEvent();
private:
friend void qtSendPendingEvents();
diff --git a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
index 1fd6e0a0c4f..ff3e67e3fc2 100644
--- a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
+++ b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
@@ -3,6 +3,7 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/private/qwasmsuspendresumecontrol_p.h>
+#include <QtCore/qdebug.h>
#include <qtwasmtestlib.h>
using namespace emscripten;
@@ -20,6 +21,7 @@ private slots:
void reuseTimer();
void cancelTimer();
void deleteTimer();
+ void suspendExclusive();
};
// Verify that a single timer fires
@@ -138,6 +140,49 @@ void WasmSuspendResumeControlTest::deleteTimer()
QWASMSUCCESS();
}
+// Verify that an exclusive suspend resumes for the exclusive event only
+void WasmSuspendResumeControlTest::suspendExclusive()
+{
+ QWasmSuspendResumeControl suspendResume;
+
+ // (re) implement a native timer - this gives us a unique event handler
+ // index which we can suspend exclusively on.
+ bool exclusiveTimerFired = false;
+ auto exclusiveTimerHandler = [&exclusiveTimerFired](emscripten::val) {
+ exclusiveTimerFired = true;
+ };
+ uint32_t exlusiveTimerHandlerIndex = suspendResume.registerEventHandler(std::move(exclusiveTimerHandler));
+
+ std::chrono::milliseconds exclusiveTimerTimeout = timerTimeout * 4;
+ double timoutValue = static_cast<double>(exclusiveTimerTimeout.count());
+ val jsHandler = suspendResume.jsEventHandlerAt(exlusiveTimerHandlerIndex);
+ val::global("window").call<double>("setTimeout", jsHandler, timoutValue);
+
+ // Schedule suppressedTimer to fire before the exclusive timer. Expected
+ // behavior is that it doesn't.
+ bool suppressedTimerFired = false;
+ QWasmTimer suppressedTimer(&suspendResume, [&suppressedTimerFired](){
+ suppressedTimerFired = true;
+ });
+ suppressedTimer.setTimeout(timerTimeout);
+
+ // Suspend exclusively for the exclusive timer, and verify that
+ // the correct timers fired.
+ suspendResume.suspendExclusive(exlusiveTimerHandlerIndex);
+ suspendResume.sendPendingEvents(); // <- also clears exclusive mode
+ if (!exclusiveTimerFired)
+ QWASMFAIL("Exclusive timer did not fire");
+ if (suppressedTimerFired)
+ QWASMFAIL("Suppressed timer did fire");
+
+ // Send (all) events, this should give is the suppressed timer
+ suspendResume.sendPendingEvents();
+ if (!suppressedTimerFired)
+ QWASMFAIL("Suppressed timer did not fire");
+
+ QWASMSUCCESS();
+}
+
int main(int argc, char **argv)
{
auto testObject = std::make_shared<WasmSuspendResumeControlTest>();
diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
index ec03c7209a4..1e49847c97f 100644
--- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
+++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
@@ -127,7 +127,7 @@ void passTest()
EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
emscripten::function("cleanupTestCase", &cleanupTestCase);
emscripten::function("getTestFunctions", &getTestFunctions);
- emscripten::function("runTestFunction", &runTestFunction);
+ emscripten::function("runTestFunction", &runTestFunction, emscripten::async());
emscripten::function("qtWasmFail", &failTest);
emscripten::function("qtWasmPass", &passTest);
}