diff options
| author | Morten Sørvig <morten.sorvig@qt.io> | 2025-08-25 16:00:26 +0200 |
|---|---|---|
| committer | Morten Sørvig <morten.sorvig@qt.io> | 2025-11-03 15:22:24 +0200 |
| commit | b06fc757661ce00da422e099eb8ff7ed51ba65ac (patch) | |
| tree | a80a8b49c6d49ace96b5e55ad4c215b0d64b4c5c | |
| parent | 77ccbd61f09c17d47b153bff41dd4a6bf7da0557 (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>
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); } |
