From 9d120b0428b4d6b2bbea34d6f04124b22051f97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 28 Oct 2025 16:07:12 +0200 Subject: [PATCH 1/2] Fix a deadlock issue with emscripten_lock_async_acquire() if user attempted to synchronously acquire the lock right after asynchronously acquiring it. https://github.com/emscripten-core/emscripten/commit/b25abd56ca6cd1947b5013a759064634bb0917ff#r168975511 --- src/lib/libwasm_worker.js | 13 ++++----- test/test_browser.py | 6 ++++ .../wasm_worker/lock_async_and_sync_acquire.c | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 test/wasm_worker/lock_async_and_sync_acquire.c diff --git a/src/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index 8e9162ecd3aca..162164072b98b 100644 --- a/src/lib/libwasm_worker.js +++ b/src/lib/libwasm_worker.js @@ -300,24 +300,21 @@ if (ENVIRONMENT_IS_WASM_WORKER emscripten_lock_async_acquire__deps: ['$polyfillWaitAsync'], emscripten_lock_async_acquire: (lock, asyncWaitFinished, userData, maxWaitMilliseconds) => { - let dispatch = (val, ret) => { - setTimeout(() => { - {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(lock, val, /*waitResult=*/ret, userData); - }, 0); - }; let tryAcquireLock = () => { do { var val = Atomics.compareExchange(HEAP32, {{{ getHeapOffset('lock', 'i32') }}}, 0/*zero represents lock being free*/, 1/*one represents lock being acquired*/); - if (!val) return dispatch(0, 0/*'ok'*/); + if (!val) return {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(lock, 0, 0/*'ok'*/, userData); var wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('lock', 'i32') }}}, val, maxWaitMilliseconds); } while (wait.value === 'not-equal'); #if ASSERTIONS assert(wait.async || wait.value === 'timed-out'); #endif if (wait.async) wait.value.then(tryAcquireLock); - else dispatch(val, 2/*'timed-out'*/); + else return {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(lock, val, 2/*'timed-out'*/, userData); }; - tryAcquireLock(); + // Asynchronously dispatch acquiring the lock so that we have uniform control flow in both + // cases when the lock is acquired, and when it needs to wait. + setTimeout(tryAcquireLock); }, emscripten_semaphore_async_acquire__deps: ['$polyfillWaitAsync'], diff --git a/test/test_browser.py b/test/test_browser.py index 224fd4af8976b..10945ad891164 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5329,6 +5329,12 @@ def test_wasm_worker_lock_wait2(self): def test_wasm_worker_lock_async_acquire(self): self.btest_exit('wasm_worker/lock_async_acquire.c', cflags=['--closure=1', '-sWASM_WORKERS']) + # Tests emscripten_lock_async_acquire() function when lock is acquired both synchronously and asynchronously. + @also_with_minimal_runtime + @flaky('https://github.com/emscripten-core/emscripten/issues/25270') + def test_wasm_worker_lock_async_and_sync_acquire(self): + self.btest('wasm_worker/lock_async_and_sync_acquire.c', expected='1', cflags=['--closure=1', '-sWASM_WORKERS']) + # Tests emscripten_lock_busyspin_wait_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_wait(self): diff --git a/test/wasm_worker/lock_async_and_sync_acquire.c b/test/wasm_worker/lock_async_and_sync_acquire.c new file mode 100644 index 0000000000000..7bcf7882b2a14 --- /dev/null +++ b/test/wasm_worker/lock_async_and_sync_acquire.c @@ -0,0 +1,29 @@ +#include +#include +#include + +emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; + +int result = 0; + +void on_acquire(volatile void* address, uint32_t value, + ATOMICS_WAIT_RESULT_T waitResult, void* userData) { + printf("on_acquire: releasing lock.\n"); + emscripten_lock_release(&lock); + printf("on_acquire: released lock.\n"); +#ifdef REPORT_RESULT + REPORT_RESULT(result); +#endif +} + +int main() { + printf("main: async acquiring lock.\n"); + emscripten_lock_async_acquire(&lock, on_acquire, 0, 100); + printf("main: busy-spin acquiring lock.\n"); + emscripten_lock_busyspin_waitinf_acquire(&lock); + printf("main: lock acquired.\n"); + emscripten_lock_release(&lock); + printf("main: lock released.\n"); + result += 1; + emscripten_exit_with_live_runtime(); +} From 5325528a8b3bdb7f004b05e58e2615ed7be74d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 28 Oct 2025 16:11:29 +0200 Subject: [PATCH 2/2] Add comment --- system/include/emscripten/wasm_worker.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index e34ec99f1803d..07715c1c14755 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -188,6 +188,10 @@ void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock __attribut // NOTE: This function can be called in both main thread and in Workers. If you // use this API in Worker, you cannot utilise an infinite loop programming // model. +// NOTE 2: This function will always acquire the lock asynchronously. That is, +// the lock will only be attempted to acquire after current control flow +// yields back to the browser, so that the Wasm call stack is empty. +// This is to guarantee an uniform control flow. void emscripten_lock_async_acquire(emscripten_lock_t *lock __attribute__((nonnull)), emscripten_async_wait_volatile_callback_t asyncWaitFinished __attribute__((nonnull)), void *userData,