Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])

Expand Down
104 changes: 60 additions & 44 deletions test/webaudio/audioworklet_emscripten_locks.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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:
Expand All @@ -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) {
Expand All @@ -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();
}