From ca6e6c866b250dac68d25c0d75d241afe6b42284 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Mon, 15 Sep 2025 15:23:26 +0200 Subject: [PATCH 01/11] Add a waiting state Also fix erroneous looking switch fall-through. --- test/webaudio/audioworklet_emscripten_locks.c | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 20471eced5f63..7482f0bab7ff5 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -17,6 +17,8 @@ int _emscripten_thread_supports_atomics_wait(void); typedef enum { + // The test hasn't yet started + TEST_NOT_STARTED, // No wait support in audio worklets TEST_HAS_WAIT, // Acquired in main, fail in process @@ -40,26 +42,17 @@ typedef enum { // Lock used in all the tests emscripten_lock_t testLock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; // Which test is running (sometimes in the worklet, sometimes in the main thread) -_Atomic Test whichTest = TEST_HAS_WAIT; +_Atomic Test whichTest = TEST_NOT_STARTED; // Time at which the test starts taken in main() double startTime = 0; bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) { assert(emscripten_current_thread_is_audio_worklet()); - // Produce at few empty frames of audio before we start trying to interact - // with the with main thread. - // On chrome at least it appears the main thread completely blocks until - // a few frames have been produced. This means it may not be safe to interact - // with the main thread during initial frames? - // In my experiments it seems like 5 was the magic number that I needed to - // produce before the main thread could continue to run. - // See https://github.com/emscripten-core/emscripten/issues/24213 - static int count = 0; - if (count++ < 5) return true; - int result = 0; switch (whichTest) { + case TEST_NOT_STARTED: + break; case TEST_HAS_WAIT: // Should not have wait support here result = _emscripten_thread_supports_atomics_wait(); @@ -80,6 +73,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, emscripten_outf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)", result); assert(!result); whichTest = TEST_WAIT_ACQUIRE; + break; case TEST_WAIT_ACQUIRE: // Will get unlocked in main thread, so should quickly acquire result = emscripten_lock_busyspin_wait_acquire(&testLock, 10000); @@ -130,6 +124,10 @@ bool MainLoop(double time, void* data) { assert(!emscripten_current_thread_is_audio_worklet()); static int didUnlock = false; switch (whichTest) { + case TEST_NOT_STARTED: + emscripten_out("Staring test (may need a button click)"); + whichTest = TEST_HAS_WAIT; + break; case TEST_WAIT_ACQUIRE: if (!didUnlock) { emscripten_out("main thread releasing lock"); @@ -178,9 +176,9 @@ int main() { startTime = emscripten_get_now(); - emscripten_set_timeout_loop(MainLoop, 10, NULL); EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL); emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL); + emscripten_set_timeout_loop(MainLoop, 10, NULL); emscripten_exit_with_live_runtime(); } From 569db6dc5c5c07bb6dff573c224bd6c58b2dca30 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Mon, 15 Sep 2025 18:11:20 +0200 Subject: [PATCH 02/11] Move work off the main thread --- test/webaudio/audioworklet_emscripten_locks.c | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 7482f0bab7ff5..fe84eb0fdfac7 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -46,6 +46,12 @@ _Atomic Test whichTest = TEST_NOT_STARTED; // Time at which the test starts taken in main() double startTime = 0; +void do_exit() { + emscripten_out("Test success"); + emscripten_terminate_all_wasm_workers(); + emscripten_force_exit(0); +} + bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) { assert(emscripten_current_thread_is_audio_worklet()); @@ -120,37 +126,39 @@ EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), { }; }); -bool MainLoop(double time, void* data) { +void WorkerLoop() { assert(!emscripten_current_thread_is_audio_worklet()); - static int didUnlock = false; - switch (whichTest) { - case TEST_NOT_STARTED: - emscripten_out("Staring test (may need a button click)"); - whichTest = TEST_HAS_WAIT; - break; - case TEST_WAIT_ACQUIRE: - if (!didUnlock) { - emscripten_out("main thread releasing lock"); - // Release here to acquire in process - emscripten_lock_release(&testLock); - didUnlock = true; - } - break; - case TEST_WAIT_INFINTE_1: - // Spin here until released in process (but don't change test until we know this case ran) - whichTest = TEST_WAIT_INFINTE_2; - emscripten_lock_busyspin_waitinf_acquire(&testLock); - emscripten_out("TEST_WAIT_INFINTE (from main)"); - break; - case TEST_DONE: - // Finished, exit from the main thread - emscripten_out("Test success"); - emscripten_force_exit(0); - return false; - default: - break; + int didUnlock = false; + while (true) { + switch (whichTest) { + case TEST_NOT_STARTED: + emscripten_out("Staring test (may need a button click)"); + whichTest = TEST_HAS_WAIT; + break; + case TEST_WAIT_ACQUIRE: + if (!didUnlock) { + emscripten_out("main thread releasing lock"); + // Release here to acquire in process + emscripten_lock_release(&testLock); + didUnlock = true; + } + break; + case TEST_WAIT_INFINTE_1: + // Spin here until released in process (but don't change test until we know this case ran) + whichTest = TEST_WAIT_INFINTE_2; + emscripten_lock_busyspin_waitinf_acquire(&testLock); + emscripten_out("TEST_WAIT_INFINTE (from main)"); + break; + case TEST_DONE: + // Finished, exit from the main thread (and return out of this loop) + emscripten_wasm_worker_post_function_v(EMSCRIPTEN_WASM_WORKER_ID_PARENT, &do_exit); + return; + default: + break; + } + // Repeat every 16ms (except when TEST_DONE) + emscripten_wasm_worker_sleep(16 * 1000000ULL); } - return true; } void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { @@ -169,16 +177,20 @@ void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool s uint8_t wasmAudioWorkletStack[2048]; int main() { - // Main thread init and acquire (work passes to the processor) + // Main thread init and acquire (work passes to the audio processor) emscripten_lock_init(&testLock); int hasLock = emscripten_lock_busyspin_wait_acquire(&testLock, 0); assert(hasLock); startTime = emscripten_get_now(); + // Audio processor callback setup EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL); emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL); - emscripten_set_timeout_loop(MainLoop, 10, NULL); + + // Worker thread setup + emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(1024); + emscripten_wasm_worker_post_function_v(worker, WorkerLoop); emscripten_exit_with_live_runtime(); } From 1db640d9bf2f375d03e9c23e85f47aa6e8ec90ef Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Mon, 15 Sep 2025 18:13:55 +0200 Subject: [PATCH 03/11] Updated docs --- test/webaudio/audioworklet_emscripten_locks.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index fe84eb0fdfac7..6fa6a0ccc9832 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -41,7 +41,7 @@ typedef enum { // Lock used in all the tests emscripten_lock_t testLock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; -// Which test is running (sometimes in the worklet, sometimes in the main thread) +// Which test is running (sometimes in the worklet, sometimes in the worker) _Atomic Test whichTest = TEST_NOT_STARTED; // Time at which the test starts taken in main() double startTime = 0; @@ -81,7 +81,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, whichTest = TEST_WAIT_ACQUIRE; break; case TEST_WAIT_ACQUIRE: - // Will get unlocked in main thread, so should quickly acquire + // Will get unlocked in worker, so should quickly acquire result = emscripten_lock_busyspin_wait_acquire(&testLock, 10000); emscripten_outf("TEST_WAIT_ACQUIRE: %d (expect: 1)", result); assert(result); @@ -96,7 +96,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, whichTest = TEST_WAIT_INFINTE_1; break; case TEST_WAIT_INFINTE_1: - // Still locked when we enter here but move on in the main thread + // Still locked when we enter here but move on in the worker break; case TEST_WAIT_INFINTE_2: emscripten_lock_release(&testLock); @@ -137,7 +137,7 @@ void WorkerLoop() { break; case TEST_WAIT_ACQUIRE: if (!didUnlock) { - emscripten_out("main thread releasing lock"); + emscripten_out("Worker releasing lock"); // Release here to acquire in process emscripten_lock_release(&testLock); didUnlock = true; @@ -147,7 +147,7 @@ void WorkerLoop() { // Spin here until released in process (but don't change test until we know this case ran) whichTest = TEST_WAIT_INFINTE_2; emscripten_lock_busyspin_waitinf_acquire(&testLock); - emscripten_out("TEST_WAIT_INFINTE (from main)"); + emscripten_out("TEST_WAIT_INFINTE (from worker)"); break; case TEST_DONE: // Finished, exit from the main thread (and return out of this loop) From d347a766e018b640b74bd01f4b7bd8a099e690b8 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Mon, 15 Sep 2025 19:16:32 +0200 Subject: [PATCH 04/11] Something to re-run the CI --- test/webaudio/audioworklet_emscripten_locks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 6fa6a0ccc9832..bd71bd302c917 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -19,7 +19,7 @@ int _emscripten_thread_supports_atomics_wait(void); typedef enum { // The test hasn't yet started TEST_NOT_STARTED, - // No wait support in audio worklets + // No atomics wait support in audio worklets TEST_HAS_WAIT, // Acquired in main, fail in process TEST_TRY_ACQUIRE, From dda7d0550bda84844e84fc8968e9ef8d1f24ff00 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 16 Sep 2025 13:40:12 +0200 Subject: [PATCH 05/11] Added docs (and moved the a spin to happen faster) --- test/webaudio/audioworklet_emscripten_locks.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index bd71bd302c917..68a81bd08bc8d 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -79,7 +79,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, emscripten_outf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)", result); assert(!result); whichTest = TEST_WAIT_ACQUIRE; - break; + // Fall through here so the worker has a chance to unlock whilst spinning case TEST_WAIT_ACQUIRE: // Will get unlocked in worker, so should quickly acquire result = emscripten_lock_busyspin_wait_acquire(&testLock, 10000); @@ -107,6 +107,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, emscripten_outf("TEST_GET_NOW: %d (expect: > 0)", result); assert(result > 0); whichTest = TEST_DONE; + // Fall through here and stop playback (shutting down in the worker) case TEST_DONE: return false; default: From 9a67852e257186fd4db8e59a6105254ca3378c25 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 16 Sep 2025 14:38:00 +0200 Subject: [PATCH 06/11] Removed flaky flag --- test/test_browser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 8c2d013a61040..f35cb078e198c 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5504,7 +5504,6 @@ def test_audio_worklet_params_mixing(self, args): # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends @requires_sound_hardware @also_with_minimal_runtime - @flaky('https://github.com/emscripten-core/emscripten/issues/25245') def test_audio_worklet_emscripten_locks(self): self.btest_exit('webaudio/audioworklet_emscripten_locks.c', cflags=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread']) From 52c6052e0c8d728187f67a0fad99a405f3061253 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 16 Sep 2025 14:43:16 +0200 Subject: [PATCH 07/11] Removed flaky import --- test/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index f35cb078e198c..7b8ace3fe3757 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -19,7 +19,7 @@ import common from common import BrowserCore, RunnerCore, path_from_root, has_browser, Reporting, is_chrome, is_firefox, CHROMIUM_BASED_BROWSERS -from common import create_file, parameterized, ensure_dir, disabled, flaky, test_file, WEBIDL_BINDER +from common import create_file, parameterized, ensure_dir, disabled, test_file, WEBIDL_BINDER from common import read_file, EMRUN, no_wasm64, no_2gb, no_4gb, copytree from common import requires_wasm2js, parameterize, find_browser_test_file, with_all_sjlj from common import also_with_minimal_runtime, also_with_wasm2js, also_with_asan, also_with_wasmfs From f19b38cd4547aa3470d9f9484e21b327087b8225 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 16 Sep 2025 16:00:36 +0200 Subject: [PATCH 08/11] Revert "Removed flaky import" This reverts commit cdc1834070d6246974ffd59f188bfa24742d2f68. --- test/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 7b8ace3fe3757..f35cb078e198c 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -19,7 +19,7 @@ import common from common import BrowserCore, RunnerCore, path_from_root, has_browser, Reporting, is_chrome, is_firefox, CHROMIUM_BASED_BROWSERS -from common import create_file, parameterized, ensure_dir, disabled, test_file, WEBIDL_BINDER +from common import create_file, parameterized, ensure_dir, disabled, flaky, test_file, WEBIDL_BINDER from common import read_file, EMRUN, no_wasm64, no_2gb, no_4gb, copytree from common import requires_wasm2js, parameterize, find_browser_test_file, with_all_sjlj from common import also_with_minimal_runtime, also_with_wasm2js, also_with_asan, also_with_wasmfs From 5983cda0fbfe4bd0310c3785b2c7f73916ac4b4f Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 16 Sep 2025 17:34:22 +0200 Subject: [PATCH 09/11] Revert "Removed flaky flag" This reverts commit 4df7999cb32fc68a9d56aaee59376005d1db0f09. --- test/test_browser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_browser.py b/test/test_browser.py index f35cb078e198c..8c2d013a61040 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5504,6 +5504,7 @@ def test_audio_worklet_params_mixing(self, args): # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends @requires_sound_hardware @also_with_minimal_runtime + @flaky('https://github.com/emscripten-core/emscripten/issues/25245') def test_audio_worklet_emscripten_locks(self): self.btest_exit('webaudio/audioworklet_emscripten_locks.c', cflags=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread']) From 04cdeb03c90a95fa72499615501a002f880f46ba Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 19 Sep 2025 13:29:08 +0200 Subject: [PATCH 10/11] Added explicit audio shutdown --- test/webaudio/audioworklet_emscripten_locks.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 68a81bd08bc8d..1a2423ace578b 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -13,6 +13,9 @@ // - emscripten_lock_release() // - emscripten_get_now() +// Global audio context +EMSCRIPTEN_WEBAUDIO_T context; + // Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) int _emscripten_thread_supports_atomics_wait(void); @@ -48,6 +51,7 @@ double startTime = 0; void do_exit() { emscripten_out("Test success"); + emscripten_destroy_audio_context(context); emscripten_terminate_all_wasm_workers(); emscripten_force_exit(0); } @@ -186,7 +190,8 @@ int main() { startTime = emscripten_get_now(); // Audio processor callback setup - EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL); + context = emscripten_create_audio_context(NULL); + assert(context); emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL); // Worker thread setup From 82405f74aa3b5036f0e3fd0b81f2fe01d65ea12d Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 19 Sep 2025 14:45:54 +0200 Subject: [PATCH 11/11] Removed flaky (again) --- test/test_browser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 8c2d013a61040..f35cb078e198c 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5504,7 +5504,6 @@ def test_audio_worklet_params_mixing(self, args): # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends @requires_sound_hardware @also_with_minimal_runtime - @flaky('https://github.com/emscripten-core/emscripten/issues/25245') def test_audio_worklet_emscripten_locks(self): self.btest_exit('webaudio/audioworklet_emscripten_locks.c', cflags=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread'])