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/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, 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(); +}