From b298f2deffeae71eb96750629df3c6d10148c311 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 17:09:03 +0100 Subject: [PATCH 01/17] 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 21fcb014305ba69bebeba14691a20a188a39f6dd Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 17:12:29 +0100 Subject: [PATCH 02/17] 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 fceee8f492989d402501640158d0b1644013048c Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 17:17:16 +0100 Subject: [PATCH 03/17] 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 8f19d2b70af750f12749a2e73fa0248b7c8a4c95 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 18:31:31 +0100 Subject: [PATCH 04/17] 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 a50d5b89d0386..24b118a5f8ce2 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5474,7 +5474,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 41391bd18f21ce7e23684ffaf3db682c576cd41e Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Thu, 4 Dec 2025 23:32:12 +0100 Subject: [PATCH 05/17] 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 d3df687faff931090cadd836b268803638ef9ccb Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 5 Dec 2025 07:52:06 +0100 Subject: [PATCH 06/17] 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 1de67c710751084b73231133aceedc1881093455 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 5 Dec 2025 12:38:21 +0100 Subject: [PATCH 07/17] 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 855e3bc9f7c81aabd64ac6ea3e3d4f3cf17dc1a4 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Fri, 5 Dec 2025 12:45:56 +0100 Subject: [PATCH 08/17] (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 54815fa44cd0c5cd8d92ebc5f8d5844a30b6dd10 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 10:50:53 +0100 Subject: [PATCH 09/17] 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 798d1e27c61dc41f1da460f95a0074b2eb17e9bc Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 11:26:00 +0100 Subject: [PATCH 10/17] 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 ce9d8662f5df733ca43b9c0a2bd9f3109b9f5f27 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 11:30:44 +0100 Subject: [PATCH 11/17] 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 0c54c71bcfade7da4759fc9fc1f91076e76db939 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 12:30:45 +0100 Subject: [PATCH 12/17] 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 868f5b3a3db2407ac819e566aa8eed81fd22a236 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 12:36:00 +0100 Subject: [PATCH 13/17] 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 dd7c0bd2f81970e0c39f66d6fd7435298361fbd2 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 17:30:15 +0100 Subject: [PATCH 14/17] 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 1e9ab3a3890533f73f65b977ae4f7a0d814649b4 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 9 Dec 2025 17:30:38 +0100 Subject: [PATCH 15/17] 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 29097a520587c87751c5a776dd8e38df93e1ffd4 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 10 Dec 2025 00:23:00 +0100 Subject: [PATCH 16/17] 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 5c2641b1d358b2ccd375e8008c8e057d3545c09c Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Wed, 10 Dec 2025 15:26:46 +0100 Subject: [PATCH 17/17] 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; }