From 810617fef57f76320d5580ffb33922930ccbc76d Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 17:09:03 +0100 Subject: [PATCH 01/19] Complete rethink of the test --- test/webaudio/audioworklet_emscripten_locks.c | 284 ++++++++++-------- 1 file changed, 154 insertions(+), 130 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 20471eced5f63..90b2684d73058 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -7,180 +6,205 @@ // // - _emscripten_thread_supports_atomics_wait() // - emscripten_lock_init() -// - emscripten_lock_try_acquire() // - emscripten_lock_busyspin_wait_acquire() -// - emscripten_lock_busyspin_waitinf_acquire() // - emscripten_lock_release() -// - emscripten_get_now() +// - emscripten_get_now() in AW + +// Build with emcc -sAUDIO_WORKLET -sWASM_WORKERS -pthread -O1 -g -o index.html audioworklet_emscripten_locks.c + +// Values -1.5373, 77.2259, -251.4728 +// Values -0.9080, -42.4902, -250.6685 + +// Marks a function to be kept in the Module and exposed to script (instead of adding to EXPORTED_FUNCTIONS) +#ifndef KEEP_IN_MODULE +#define KEEP_IN_MODULE __attribute__((used, visibility("default"))) +#endif + +// This needs to be big enough for a stereo output (1024 with a 128 frame) + working stack +#define AUDIO_STACK_SIZE 2048 // 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 - TEST_HAS_WAIT, - // Acquired in main, fail in process - TEST_TRY_ACQUIRE, - // Keep acquired so time-out - TEST_WAIT_ACQUIRE_FAIL, - // Release in main, succeed in process - TEST_WAIT_ACQUIRE, - // Release in process after above - TEST_RELEASE, - // Released in process above, spin in main - TEST_WAIT_INFINTE_1, - // Release in process to stop spinning in main - TEST_WAIT_INFINTE_2, - // Call emscripten_get_now() in process - TEST_GET_NOW, + // The test hasn't yet started + TEST_NOT_STARTED, + // Worklet ready and running the test + TEST_RUNNING, + // Main thread is finished, wait on worklet + TEST_DONE_MAIN, // Test finished TEST_DONE } Test; +// Global audio context +EMSCRIPTEN_WEBAUDIO_T context; // 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()); +// Counter for main, accessed only by main +int howManyMain = 0; +// Counter for the audio worklet, accessed only by the AW +int howManyProc = 0; + +// Our dummy container +typedef struct Dummy { + uint32_t val0; + uint32_t val1; + uint32_t val2; +} Dummy; + +// Start values +void initDummy(Dummy* dummy) { + dummy->val0 = 4; + dummy->val1 = 1; + dummy->val2 = 2; +} - // 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_HAS_WAIT: - // Should not have wait support here - result = _emscripten_thread_supports_atomics_wait(); - emscripten_outf("TEST_HAS_WAIT: %d (expect: 0)", result); - assert(!result); - whichTest = TEST_TRY_ACQUIRE; - break; - case TEST_TRY_ACQUIRE: - // Was locked after init, should fail to acquire - result = emscripten_lock_try_acquire(&testLock); - emscripten_outf("TEST_TRY_ACQUIRE: %d (expect: 0)", result); - assert(!result); - whichTest = TEST_WAIT_ACQUIRE_FAIL; - break; - case TEST_WAIT_ACQUIRE_FAIL: - // Still locked so we fail to acquire - result = emscripten_lock_busyspin_wait_acquire(&testLock, 100); - emscripten_outf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)", result); - assert(!result); - whichTest = TEST_WAIT_ACQUIRE; - case TEST_WAIT_ACQUIRE: - // Will get unlocked in main thread, so should quickly acquire - result = emscripten_lock_busyspin_wait_acquire(&testLock, 10000); - emscripten_outf("TEST_WAIT_ACQUIRE: %d (expect: 1)", result); - assert(result); - whichTest = TEST_RELEASE; - break; - case TEST_RELEASE: - // Unlock, check the result +void printDummy(Dummy* dummy) { + emscripten_outf("Values: %u, %u, %u", dummy->val0, dummy->val1, dummy->val2); +} + +// Run a simple calculation that will only be stable *if* all values are atomically updated +void runCalcs(Dummy* dummy, int num) { + for (int n = 0; n < num; n++) { + int have = emscripten_lock_busyspin_wait_acquire(&testLock, 100); + assert(have); + dummy->val0 += dummy->val1 * dummy->val2; + dummy->val1 += dummy->val2 * dummy->val0; + dummy->val2 += dummy->val0 * dummy->val1; + dummy->val0 /= 4; + dummy->val1 /= 3; + dummy->val2 /= 2; emscripten_lock_release(&testLock); - result = emscripten_lock_try_acquire(&testLock); - emscripten_outf("TEST_RELEASE: %d (expect: 1)", result); - assert(result); - whichTest = TEST_WAIT_INFINTE_1; - break; - case TEST_WAIT_INFINTE_1: - // Still locked when we enter here but move on in the main thread + } +} + +void stopping() { + emscripten_out("Expect: 949807601, 1303780836, 243502614"); + emscripten_out("Ending test"); + emscripten_destroy_audio_context(context); + emscripten_force_exit(0); +} + +// AW callback +bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) { + assert(emscripten_current_thread_is_audio_worklet()); + switch (whichTest) { + case TEST_NOT_STARTED: + whichTest = TEST_RUNNING; break; - case TEST_WAIT_INFINTE_2: - emscripten_lock_release(&testLock); - whichTest = TEST_GET_NOW; + case TEST_RUNNING: + case TEST_DONE_MAIN: + if (howManyProc-- > 0) { + runCalcs((Dummy*) data, 250); + } else { + if (whichTest == TEST_DONE_MAIN) { + // Both loops are finished + whichTest = TEST_DONE; + } + } break; - case TEST_GET_NOW: - result = (int) (emscripten_get_now() - startTime); - emscripten_outf("TEST_GET_NOW: %d (expect: > 0)", result); - assert(result > 0); - whichTest = TEST_DONE; case TEST_DONE: + emscripten_outf("Took %dms (expect: > 0)", (int) (emscripten_get_now() - startTime)); return false; - default: - break; } return true; } -EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), { - let startButton = document.createElement('button'); - startButton.innerHTML = 'Start playback'; - document.body.appendChild(startButton); - - audioContext = emscriptenGetAudioObject(audioContext); - startButton.onclick = () => { - audioContext.resume(); - }; -}); - -bool MainLoop(double time, void* data) { +// Main thread callback +bool mainLoop(double time, void* data) { 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; + case TEST_NOT_STARTED: + break; + case TEST_RUNNING: + if (howManyMain-- > 0) { + runCalcs((Dummy*) data, 1000); + } else { + // Done here, so signal to process() + whichTest = TEST_DONE_MAIN; } 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)"); + case TEST_DONE_MAIN: + // Wait for process() to finish break; case TEST_DONE: - // Finished, exit from the main thread - emscripten_out("Test success"); - emscripten_force_exit(0); + printDummy((Dummy*) data); + // 32-bit maths with locks *should* result in these: + assert(((Dummy*) data)->val0 == 949807601 + && ((Dummy*) data)->val1 == 1303780836 + && ((Dummy*) data)->val2 == 243502614); + stopping(); return false; - default: - break; } return true; } -void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { - int outputChannelCounts[1] = { 1 }; - EmscriptenAudioWorkletNodeCreateOptions options = { .numberOfInputs = 0, .numberOfOutputs = 1, .outputChannelCounts = outputChannelCounts }; - EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, NULL); - emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0); - InitHtmlUi(audioContext); +KEEP_IN_MODULE void startTest() { + startTime = emscripten_get_now(); + if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) { + emscripten_resume_audio_context_sync(context); + } + howManyMain = 200; + howManyProc = 200; } -void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { - WebAudioWorkletProcessorCreateOptions opts = { .name = "noise-generator" }; - emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, NULL); +// HTML button to manually run the test +EM_JS(void, addButton, (), { + var button = document.createElement("button"); + button.appendChild(document.createTextNode("Start Test")); + document.body.appendChild(button); + document.onclick = () => { + if (globalThis._startTest) { + _startTest(); + } + }; +}); + +// Audio processor created, now register the audio callback +void processorCreated(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) { + assert(success && "Audio worklet failed in processorCreated()"); + emscripten_out("Audio worklet processor created"); + // Single stereo output + int outputChannelCounts[1] = { 1 }; + EmscriptenAudioWorkletNodeCreateOptions opts = { + .numberOfOutputs = 1, + .outputChannelCounts = outputChannelCounts + }; + EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(ctx, "locks-test", &opts, &process, data); + emscripten_audio_node_connect(worklet, ctx, 0, 0); } -uint8_t wasmAudioWorkletStack[2048]; +// Worklet thread inited, now create the audio processor +void initialised(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) { + assert(success && "Audio worklet failed in initialised()"); + emscripten_out("Audio worklet initialised"); + WebAudioWorkletProcessorCreateOptions opts = { + .name = "locks-test" + }; + emscripten_create_wasm_audio_worklet_processor_async(ctx, &opts, &processorCreated, data); +} int main() { - // Main thread init and acquire (work passes to the 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); - emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL); + Dummy* dummy = (Dummy*) malloc(sizeof(Dummy)); + initDummy(dummy); + + char* const workletStack = memalign(16, AUDIO_STACK_SIZE); + assert(workletStack); + // Audio processor callback setup + context = emscripten_create_audio_context(NULL); + assert(context); + emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, dummy); + + emscripten_set_timeout_loop(mainLoop, 10, dummy); + addButton(); + startTest(); // <-- May need a manual click to start emscripten_exit_with_live_runtime(); } From e9ee1f9dbee367710b5aec11a4e284fcc86ce9ce Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 17:12:29 +0100 Subject: [PATCH 02/19] Minor tidy --- test/webaudio/audioworklet_emscripten_locks.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 90b2684d73058..f9787e7fe526a 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -10,11 +10,6 @@ // - emscripten_lock_release() // - emscripten_get_now() in AW -// Build with emcc -sAUDIO_WORKLET -sWASM_WORKERS -pthread -O1 -g -o index.html audioworklet_emscripten_locks.c - -// Values -1.5373, 77.2259, -251.4728 -// Values -0.9080, -42.4902, -250.6685 - // Marks a function to be kept in the Module and exposed to script (instead of adding to EXPORTED_FUNCTIONS) #ifndef KEEP_IN_MODULE #define KEEP_IN_MODULE __attribute__((used, visibility("default"))) From 61cc4acfe06afa1fd2f4832053a22877dc53f6bd Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 17:17:16 +0100 Subject: [PATCH 03/19] Minor tidy --- 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 f9787e7fe526a..5aeb4b1d0cecb 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -47,7 +47,7 @@ int howManyMain = 0; int howManyProc = 0; // Our dummy container -typedef struct Dummy { +typedef struct { uint32_t val0; uint32_t val1; uint32_t val2; From daed9e18392c713a8bc38c0ed6c188499c5e70e6 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 18:31:31 +0100 Subject: [PATCH 04/19] Should be flake-free --- test/test_browser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 3a072c4221d4d..32dd8c2eb0e0e 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5476,7 +5476,6 @@ def test_audio_worklet_params_mixing(self, args): @requires_sound_hardware @requires_shared_array_buffer @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 611493a086b80028eac64c778d153344f80523da Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 23:32:12 +0100 Subject: [PATCH 05/19] Lowered lock wait time --- 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 5aeb4b1d0cecb..21b97a8f36e36 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -67,7 +67,7 @@ void printDummy(Dummy* dummy) { // Run a simple calculation that will only be stable *if* all values are atomically updated void runCalcs(Dummy* dummy, int num) { for (int n = 0; n < num; n++) { - int have = emscripten_lock_busyspin_wait_acquire(&testLock, 100); + int have = emscripten_lock_busyspin_wait_acquire(&testLock, 10); assert(have); dummy->val0 += dummy->val1 * dummy->val2; dummy->val1 += dummy->val2 * dummy->val0; From 1d0dc8d6c9eb71d247088c4082fb55f043ec989b Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 5 Dec 2025 07:52:06 +0100 Subject: [PATCH 06/19] Something to re-run blocked 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 21b97a8f36e36..a3bf9638bd12e 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -165,7 +165,7 @@ EM_JS(void, addButton, (), { void processorCreated(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) { assert(success && "Audio worklet failed in processorCreated()"); emscripten_out("Audio worklet processor created"); - // Single stereo output + // Single mono output int outputChannelCounts[1] = { 1 }; EmscriptenAudioWorkletNodeCreateOptions opts = { .numberOfOutputs = 1, From d1600169bd6265cb34182dc081d73dd741e45ba0 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 5 Dec 2025 12:38:21 +0100 Subject: [PATCH 07/19] Improved symmetry The test function is called approx. 200'000x from each thread. --- test/webaudio/audioworklet_emscripten_locks.c | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index a3bf9638bd12e..36d56ecb413a7 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -18,6 +18,9 @@ // This needs to be big enough for a stereo output (1024 with a 128 frame) + working stack #define AUDIO_STACK_SIZE 2048 +// Define DISABLE_LOCKS to run the test without locking, which should statistically always fail +//#define DISABLE_LOCKS + // Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) int _emscripten_thread_supports_atomics_wait(void); @@ -65,10 +68,13 @@ void printDummy(Dummy* dummy) { } // Run a simple calculation that will only be stable *if* all values are atomically updated +// (Currently called approx. 200'000x from each thread) void runCalcs(Dummy* dummy, int num) { for (int n = 0; n < num; n++) { +#ifndef DISABLE_LOCKS int have = emscripten_lock_busyspin_wait_acquire(&testLock, 10); assert(have); +#endif dummy->val0 += dummy->val1 * dummy->val2; dummy->val1 += dummy->val2 * dummy->val0; dummy->val2 += dummy->val0 * dummy->val1; @@ -80,7 +86,7 @@ void runCalcs(Dummy* dummy, int num) { } void stopping() { - emscripten_out("Expect: 949807601, 1303780836, 243502614"); + emscripten_out("Expect: 811100370, 759556424, 723197652"); emscripten_out("Ending test"); emscripten_destroy_audio_context(context); emscripten_force_exit(0); @@ -96,16 +102,16 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi case TEST_RUNNING: case TEST_DONE_MAIN: if (howManyProc-- > 0) { - runCalcs((Dummy*) data, 250); + runCalcs((Dummy*) data, 267); // <-- process gets called 3.75x more than main } else { if (whichTest == TEST_DONE_MAIN) { + emscripten_outf("Worklet done after %dms (expect: > 2s)", (int) (emscripten_get_now() - startTime)); // Both loops are finished whichTest = TEST_DONE; } } break; case TEST_DONE: - emscripten_outf("Took %dms (expect: > 0)", (int) (emscripten_get_now() - startTime)); return false; } return true; @@ -121,6 +127,7 @@ bool mainLoop(double time, void* data) { if (howManyMain-- > 0) { runCalcs((Dummy*) data, 1000); } else { + emscripten_outf("Main thread done after %dms (expect: > 2s)", (int) (emscripten_get_now() - startTime)); // Done here, so signal to process() whichTest = TEST_DONE_MAIN; } @@ -131,9 +138,9 @@ bool mainLoop(double time, void* data) { case TEST_DONE: printDummy((Dummy*) data); // 32-bit maths with locks *should* result in these: - assert(((Dummy*) data)->val0 == 949807601 - && ((Dummy*) data)->val1 == 1303780836 - && ((Dummy*) data)->val2 == 243502614); + assert(((Dummy*) data)->val0 == 811100370 + && ((Dummy*) data)->val1 == 759556424 + && ((Dummy*) data)->val2 == 723197652); stopping(); return false; } @@ -146,7 +153,7 @@ KEEP_IN_MODULE void startTest() { emscripten_resume_audio_context_sync(context); } howManyMain = 200; - howManyProc = 200; + howManyProc = 750; // <-- process gets called 3.75x more than main } // HTML button to manually run the test From 0ad1342a0900fe68fba3f857230eb4c5bfcabaa8 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 5 Dec 2025 12:45:56 +0100 Subject: [PATCH 08/19] (Failure due to Firefox not downloading on 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 36d56ecb413a7..f9459c3aa7844 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -68,7 +68,7 @@ void printDummy(Dummy* dummy) { } // Run a simple calculation that will only be stable *if* all values are atomically updated -// (Currently called approx. 200'000x from each thread) +// (Currently approx. 200'000x from each thread) void runCalcs(Dummy* dummy, int num) { for (int n = 0; n < num; n++) { #ifndef DISABLE_LOCKS From d367b1fd9a32bbc7017c6090e8482f322e2bc7f2 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 10:50:53 +0100 Subject: [PATCH 09/19] Moved to EMSCRIPTEN_KEEPALIVE --- test/webaudio/audioworklet_emscripten_locks.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index f9459c3aa7844..998182f8a8029 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -10,11 +10,6 @@ // - emscripten_lock_release() // - emscripten_get_now() in AW -// Marks a function to be kept in the Module and exposed to script (instead of adding to EXPORTED_FUNCTIONS) -#ifndef KEEP_IN_MODULE -#define KEEP_IN_MODULE __attribute__((used, visibility("default"))) -#endif - // This needs to be big enough for a stereo output (1024 with a 128 frame) + working stack #define AUDIO_STACK_SIZE 2048 @@ -147,7 +142,7 @@ bool mainLoop(double time, void* data) { return true; } -KEEP_IN_MODULE void startTest() { +EMSCRIPTEN_KEEPALIVE void startTest() { startTime = emscripten_get_now(); if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) { emscripten_resume_audio_context_sync(context); From dca52af9d9e2bf0e1093955c79e7a473d9adf12b Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 11:26:00 +0100 Subject: [PATCH 10/19] Removed magic numbers --- test/webaudio/audioworklet_emscripten_locks.c | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 998182f8a8029..5ceefbecf0a3f 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -14,7 +14,7 @@ #define AUDIO_STACK_SIZE 2048 // Define DISABLE_LOCKS to run the test without locking, which should statistically always fail -//#define DISABLE_LOCKS +#define DISABLE_LOCKS // Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) int _emscripten_thread_supports_atomics_wait(void); @@ -53,13 +53,13 @@ typedef struct { // Start values void initDummy(Dummy* dummy) { - dummy->val0 = 4; + dummy->val0 = 1; dummy->val1 = 1; - dummy->val2 = 2; + dummy->val2 = 1; } void printDummy(Dummy* dummy) { - emscripten_outf("Values: %u, %u, %u", dummy->val0, dummy->val1, dummy->val2); + emscripten_outf("Values: 0x%08X, 0x%08X, 0x%08X", dummy->val0, dummy->val1, dummy->val2); } // Run a simple calculation that will only be stable *if* all values are atomically updated @@ -70,18 +70,20 @@ void runCalcs(Dummy* dummy, int num) { int have = emscripten_lock_busyspin_wait_acquire(&testLock, 10); assert(have); #endif - dummy->val0 += dummy->val1 * dummy->val2; - dummy->val1 += dummy->val2 * dummy->val0; - dummy->val2 += dummy->val0 * dummy->val1; - dummy->val0 /= 4; - dummy->val1 /= 3; - dummy->val2 /= 2; + dummy->val0 += dummy->val2 * 7; + dummy->val1 += dummy->val0 * 7; + dummy->val2 += dummy->val1 * 7; + dummy->val0 += dummy->val2 / 3; + dummy->val1 += dummy->val0 / 3; + dummy->val2 += dummy->val1 / 3; +#ifndef DISABLE_LOCKS emscripten_lock_release(&testLock); +#endif } } void stopping() { - emscripten_out("Expect: 811100370, 759556424, 723197652"); + emscripten_out("Expect: all values are equal"); emscripten_out("Ending test"); emscripten_destroy_audio_context(context); emscripten_force_exit(0); @@ -132,10 +134,8 @@ bool mainLoop(double time, void* data) { break; case TEST_DONE: printDummy((Dummy*) data); - // 32-bit maths with locks *should* result in these: - assert(((Dummy*) data)->val0 == 811100370 - && ((Dummy*) data)->val1 == 759556424 - && ((Dummy*) data)->val2 == 723197652); + assert(((Dummy*) data)->val0 == ((Dummy*) data)->val1 + && ((Dummy*) data)->val1 == ((Dummy*) data)->val2); stopping(); return false; } From 79b66687304c9e2a65ec0f492b619574d2abcee1 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 11:30:44 +0100 Subject: [PATCH 11/19] Re-enabled locks --- 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 5ceefbecf0a3f..f0bc7537979e6 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -14,7 +14,7 @@ #define AUDIO_STACK_SIZE 2048 // Define DISABLE_LOCKS to run the test without locking, which should statistically always fail -#define DISABLE_LOCKS +//#define DISABLE_LOCKS // Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) int _emscripten_thread_supports_atomics_wait(void); From 77d849a22796390d0e0c8d5f0a5687bdae883d76 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 12:30:45 +0100 Subject: [PATCH 12/19] Revert "Removed magic numbers" This reverts commit 5677c957993a6e4d326288760b9c33688ac0ac26. --- test/webaudio/audioworklet_emscripten_locks.c | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index f0bc7537979e6..998182f8a8029 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -53,13 +53,13 @@ typedef struct { // Start values void initDummy(Dummy* dummy) { - dummy->val0 = 1; + dummy->val0 = 4; dummy->val1 = 1; - dummy->val2 = 1; + dummy->val2 = 2; } void printDummy(Dummy* dummy) { - emscripten_outf("Values: 0x%08X, 0x%08X, 0x%08X", dummy->val0, dummy->val1, dummy->val2); + emscripten_outf("Values: %u, %u, %u", dummy->val0, dummy->val1, dummy->val2); } // Run a simple calculation that will only be stable *if* all values are atomically updated @@ -70,20 +70,18 @@ void runCalcs(Dummy* dummy, int num) { int have = emscripten_lock_busyspin_wait_acquire(&testLock, 10); assert(have); #endif - dummy->val0 += dummy->val2 * 7; - dummy->val1 += dummy->val0 * 7; - dummy->val2 += dummy->val1 * 7; - dummy->val0 += dummy->val2 / 3; - dummy->val1 += dummy->val0 / 3; - dummy->val2 += dummy->val1 / 3; -#ifndef DISABLE_LOCKS + dummy->val0 += dummy->val1 * dummy->val2; + dummy->val1 += dummy->val2 * dummy->val0; + dummy->val2 += dummy->val0 * dummy->val1; + dummy->val0 /= 4; + dummy->val1 /= 3; + dummy->val2 /= 2; emscripten_lock_release(&testLock); -#endif } } void stopping() { - emscripten_out("Expect: all values are equal"); + emscripten_out("Expect: 811100370, 759556424, 723197652"); emscripten_out("Ending test"); emscripten_destroy_audio_context(context); emscripten_force_exit(0); @@ -134,8 +132,10 @@ bool mainLoop(double time, void* data) { break; case TEST_DONE: printDummy((Dummy*) data); - assert(((Dummy*) data)->val0 == ((Dummy*) data)->val1 - && ((Dummy*) data)->val1 == ((Dummy*) data)->val2); + // 32-bit maths with locks *should* result in these: + assert(((Dummy*) data)->val0 == 811100370 + && ((Dummy*) data)->val1 == 759556424 + && ((Dummy*) data)->val2 == 723197652); stopping(); return false; } From 1519cf067d1d7531cb1d801e45fa39061b422e99 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 12:36:00 +0100 Subject: [PATCH 13/19] Minor tidy --- test/webaudio/audioworklet_emscripten_locks.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 998182f8a8029..5dfdb63b70e46 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -59,7 +59,7 @@ void initDummy(Dummy* dummy) { } void printDummy(Dummy* dummy) { - emscripten_outf("Values: %u, %u, %u", dummy->val0, dummy->val1, dummy->val2); + emscripten_outf("Values: 0x%08X, 0x%08X, 0x%08X", dummy->val0, dummy->val1, dummy->val2); } // Run a simple calculation that will only be stable *if* all values are atomically updated @@ -76,12 +76,14 @@ void runCalcs(Dummy* dummy, int num) { dummy->val0 /= 4; dummy->val1 /= 3; dummy->val2 /= 2; +#ifndef DISABLE_LOCKS emscripten_lock_release(&testLock); +#endif } } void stopping() { - emscripten_out("Expect: 811100370, 759556424, 723197652"); + emscripten_out("Expect: 0x305868D2, 0x2D45E948, 0x2B1B1ED4"); emscripten_out("Ending test"); emscripten_destroy_audio_context(context); emscripten_force_exit(0); @@ -133,9 +135,9 @@ bool mainLoop(double time, void* data) { case TEST_DONE: printDummy((Dummy*) data); // 32-bit maths with locks *should* result in these: - assert(((Dummy*) data)->val0 == 811100370 - && ((Dummy*) data)->val1 == 759556424 - && ((Dummy*) data)->val2 == 723197652); + assert(((Dummy*) data)->val0 == 0x305868D2 + && ((Dummy*) data)->val1 == 0x2D45E948 + && ((Dummy*) data)->val2 == 0x2B1B1ED4); stopping(); return false; } From f79b4b687118b3ba00adc65c64337919cdda72aa Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 17:30:15 +0100 Subject: [PATCH 14/19] Pre-calculate magic numbers --- test/webaudio/audioworklet_emscripten_locks.c | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 5dfdb63b70e46..0457ad0442acc 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -14,7 +14,16 @@ #define AUDIO_STACK_SIZE 2048 // Define DISABLE_LOCKS to run the test without locking, which should statistically always fail -//#define DISABLE_LOCKS +#define DISABLE_LOCKS + +// Number of times mainLoop() calculations get called +#define MAINLOOP_CALCS 10000 +// Number of times MAINLOOP_CALCS are performed +#define MAINLOOP_RUNS 200 +// Number of times process() calculations get called (called 3.75x more than mainLoop) +#define PROCESS_CALCS 2667 +// Number of times PROCESS_CALCS are performed (3.75x more than mainLoop) +#define PROCESS_RUNS 750 // Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) int _emscripten_thread_supports_atomics_wait(void); @@ -51,6 +60,11 @@ typedef struct { uint32_t val2; } Dummy; +// Container used to run the test +Dummy testData; +// Container to hold the expected value +Dummy trueData; + // Start values void initDummy(Dummy* dummy) { dummy->val0 = 4; @@ -83,8 +97,7 @@ void runCalcs(Dummy* dummy, int num) { } void stopping() { - emscripten_out("Expect: 0x305868D2, 0x2D45E948, 0x2B1B1ED4"); - emscripten_out("Ending test"); + emscripten_out("Test done"); emscripten_destroy_audio_context(context); emscripten_force_exit(0); } @@ -99,7 +112,7 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi case TEST_RUNNING: case TEST_DONE_MAIN: if (howManyProc-- > 0) { - runCalcs((Dummy*) data, 267); // <-- process gets called 3.75x more than main + runCalcs((Dummy*) data, PROCESS_CALCS); } else { if (whichTest == TEST_DONE_MAIN) { emscripten_outf("Worklet done after %dms (expect: > 2s)", (int) (emscripten_get_now() - startTime)); @@ -122,7 +135,7 @@ bool mainLoop(double time, void* data) { break; case TEST_RUNNING: if (howManyMain-- > 0) { - runCalcs((Dummy*) data, 1000); + runCalcs((Dummy*) data, MAINLOOP_CALCS); } else { emscripten_outf("Main thread done after %dms (expect: > 2s)", (int) (emscripten_get_now() - startTime)); // Done here, so signal to process() @@ -133,11 +146,11 @@ bool mainLoop(double time, void* data) { // Wait for process() to finish break; case TEST_DONE: + emscripten_out("Multi-thread results:"); printDummy((Dummy*) data); - // 32-bit maths with locks *should* result in these: - assert(((Dummy*) data)->val0 == 0x305868D2 - && ((Dummy*) data)->val1 == 0x2D45E948 - && ((Dummy*) data)->val2 == 0x2B1B1ED4); + assert(((Dummy*) data)->val0 == trueData.val0 + && ((Dummy*) data)->val1 == trueData.val1 + && ((Dummy*) data)->val2 == trueData.val2); stopping(); return false; } @@ -149,8 +162,8 @@ EMSCRIPTEN_KEEPALIVE void startTest() { if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) { emscripten_resume_audio_context_sync(context); } - howManyMain = 200; - howManyProc = 750; // <-- process gets called 3.75x more than main + howManyMain = MAINLOOP_RUNS; + howManyProc = PROCESS_RUNS; } // HTML button to manually run the test @@ -191,17 +204,26 @@ void initialised(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) { int main() { emscripten_lock_init(&testLock); - Dummy* dummy = (Dummy*) malloc(sizeof(Dummy)); - initDummy(dummy); + initDummy(&testData); + initDummy(&trueData); + // Canonical results, run in a single thread + for (int n = MAINLOOP_RUNS; n > 0; n--) { + runCalcs(&trueData, MAINLOOP_CALCS); + } + for (int n = PROCESS_RUNS; n > 0; n--) { + runCalcs(&trueData, PROCESS_CALCS); + } + emscripten_out("Single-thread results:"); + printDummy(&trueData); char* const workletStack = memalign(16, AUDIO_STACK_SIZE); assert(workletStack); // Audio processor callback setup context = emscripten_create_audio_context(NULL); assert(context); - emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, dummy); + emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, &testData); - emscripten_set_timeout_loop(mainLoop, 10, dummy); + emscripten_set_timeout_loop(mainLoop, 10, &testData); addButton(); startTest(); // <-- May need a manual click to start From ecd0b3300306c167efff57431a2119c00ecee770 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 17:30:38 +0100 Subject: [PATCH 15/19] Ahem, re-enable locks --- 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 0457ad0442acc..29af77c936e42 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -14,7 +14,7 @@ #define AUDIO_STACK_SIZE 2048 // Define DISABLE_LOCKS to run the test without locking, which should statistically always fail -#define DISABLE_LOCKS +//#define DISABLE_LOCKS // Number of times mainLoop() calculations get called #define MAINLOOP_CALCS 10000 From f790b35498e933fb2433a116c2cc38222eb1542f Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 10 Dec 2025 00:23:00 +0100 Subject: [PATCH 16/19] Very, very long lock acquire wait (for slower 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 29af77c936e42..490ef64bcf434 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -81,7 +81,7 @@ void printDummy(Dummy* dummy) { void runCalcs(Dummy* dummy, int num) { for (int n = 0; n < num; n++) { #ifndef DISABLE_LOCKS - int have = emscripten_lock_busyspin_wait_acquire(&testLock, 10); + int have = emscripten_lock_busyspin_wait_acquire(&testLock, 1000); assert(have); #endif dummy->val0 += dummy->val1 * dummy->val2; From 4d98a3e32f2f9b06d6412e39949ba97e86289cd3 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 10 Dec 2025 15:26:46 +0100 Subject: [PATCH 17/19] Clarified text --- test/webaudio/audioworklet_emscripten_locks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 490ef64bcf434..56aee3bdb88f4 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -115,7 +115,7 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi runCalcs((Dummy*) data, PROCESS_CALCS); } else { if (whichTest == TEST_DONE_MAIN) { - emscripten_outf("Worklet done after %dms (expect: > 2s)", (int) (emscripten_get_now() - startTime)); + emscripten_outf("Worklet done after %dms (expect: approx. 2s)", (int) (emscripten_get_now() - startTime)); // Both loops are finished whichTest = TEST_DONE; } @@ -137,7 +137,7 @@ bool mainLoop(double time, void* data) { if (howManyMain-- > 0) { runCalcs((Dummy*) data, MAINLOOP_CALCS); } else { - emscripten_outf("Main thread done after %dms (expect: > 2s)", (int) (emscripten_get_now() - startTime)); + emscripten_outf("Main thread done after %dms (expect: approx. 2s)", (int) (emscripten_get_now() - startTime)); // Done here, so signal to process() whichTest = TEST_DONE_MAIN; } From 7cab23ab3bc8660ce543631bcc5a1e83b2c1def0 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 23 Dec 2025 19:19:03 +0100 Subject: [PATCH 18/19] Button can only be clicked once --- test/webaudio/audioworklet_emscripten_locks.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 56aee3bdb88f4..9bf91059007d2 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -158,12 +158,16 @@ bool mainLoop(double time, void* data) { } EMSCRIPTEN_KEEPALIVE void startTest() { - startTime = emscripten_get_now(); - if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) { - emscripten_resume_audio_context_sync(context); + if (whichTest == TEST_NOT_STARTED) { + startTime = emscripten_get_now(); + if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) { + emscripten_resume_audio_context_sync(context); + } + howManyMain = MAINLOOP_RUNS; + howManyProc = PROCESS_RUNS; + } else { + emscripten_out("Reload page to re-run"); } - howManyMain = MAINLOOP_RUNS; - howManyProc = PROCESS_RUNS; } // HTML button to manually run the test @@ -172,9 +176,7 @@ EM_JS(void, addButton, (), { button.appendChild(document.createTextNode("Start Test")); document.body.appendChild(button); document.onclick = () => { - if (globalThis._startTest) { - _startTest(); - } + _startTest(); }; }); From 5b023dbd46260d474cdc0d9fca074e4b941b9311 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 23 Dec 2025 19:28:47 +0100 Subject: [PATCH 19/19] Used global structs instead of passing as a param --- test/webaudio/audioworklet_emscripten_locks.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 9bf91059007d2..f34ea00c296ba 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -112,7 +112,7 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi case TEST_RUNNING: case TEST_DONE_MAIN: if (howManyProc-- > 0) { - runCalcs((Dummy*) data, PROCESS_CALCS); + runCalcs(&testData, PROCESS_CALCS); } else { if (whichTest == TEST_DONE_MAIN) { emscripten_outf("Worklet done after %dms (expect: approx. 2s)", (int) (emscripten_get_now() - startTime)); @@ -135,7 +135,7 @@ bool mainLoop(double time, void* data) { break; case TEST_RUNNING: if (howManyMain-- > 0) { - runCalcs((Dummy*) data, MAINLOOP_CALCS); + runCalcs(&testData, MAINLOOP_CALCS); } else { emscripten_outf("Main thread done after %dms (expect: approx. 2s)", (int) (emscripten_get_now() - startTime)); // Done here, so signal to process() @@ -147,10 +147,10 @@ bool mainLoop(double time, void* data) { break; case TEST_DONE: emscripten_out("Multi-thread results:"); - printDummy((Dummy*) data); - assert(((Dummy*) data)->val0 == trueData.val0 - && ((Dummy*) data)->val1 == trueData.val1 - && ((Dummy*) data)->val2 == trueData.val2); + printDummy(&testData); + assert(testData.val0 == trueData.val0 + && testData.val1 == trueData.val1 + && testData.val2 == trueData.val2); stopping(); return false; } @@ -223,9 +223,9 @@ int main() { // Audio processor callback setup context = emscripten_create_audio_context(NULL); assert(context); - emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, &testData); + emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, NULL); - emscripten_set_timeout_loop(mainLoop, 10, &testData); + emscripten_set_timeout_loop(mainLoop, 10, NULL); addButton(); startTest(); // <-- May need a manual click to start