diff --git a/test/test_browser.py b/test/test_browser.py index 5474978a08f75..effcbbffcc243 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5500,7 +5500,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']) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 20471eced5f63..1a2423ace578b 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -13,11 +13,16 @@ // - 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); typedef enum { - // No wait support in audio worklets + // The test hasn't yet started + TEST_NOT_STARTED, + // No atomics wait support in audio worklets TEST_HAS_WAIT, // Acquired in main, fail in process TEST_TRY_ACQUIRE, @@ -39,27 +44,25 @@ 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; +// 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; +void do_exit() { + emscripten_out("Test success"); + emscripten_destroy_audio_context(context); + 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()); - // 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,8 +83,9 @@ 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; + // Fall through here so the worker has a chance to unlock whilst spinning 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 +100,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); @@ -107,6 +111,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: @@ -126,33 +131,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_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("Worker 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 worker)"); + 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) { @@ -171,16 +182,21 @@ 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(); - emscripten_set_timeout_loop(MainLoop, 10, NULL); - EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL); + // Audio processor callback setup + context = emscripten_create_audio_context(NULL); + assert(context); emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, 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(); }