Path: blob/main/system/lib/pthread/emscripten_futex_wait.c
6172 views
/*1* Copyright 2021 The Emscripten Authors. All rights reserved.2* Emscripten is available under two separate licenses, the MIT license and the3* University of Illinois/NCSA Open Source License. Both these licenses can be4* found in the LICENSE file.5*/6#include <assert.h>7#include <errno.h>8#include <math.h>9#include <emscripten/threading.h>10#include <stdlib.h>11#include <stdio.h>12#include <sys/param.h>13#include "atomic.h"14#include "threading_internal.h"15#include "pthread_impl.h"1617extern void* _emscripten_main_thread_futex;1819static int futex_wait_main_browser_thread(volatile void* addr,20uint32_t val,21double timeout) {22// Atomics.wait is not available in the main browser thread, so simulate it23// via busy spinning. Only the main browser thread is allowed to call into24// this function. It is not thread-safe to be called from any other thread.25assert(emscripten_is_main_browser_thread());2627double now = emscripten_get_now();28double end = now + timeout;2930// Register globally which address the main thread is simulating to be waiting31// on. When zero, the main thread is not waiting on anything, and on nonzero,32// the contents of the address pointed by _emscripten_main_thread_futex tell33// which address the main thread is simulating its wait on. We need to be34// careful of recursion here: If we wait on a futex, and then call35// _emscripten_yield() below, that will call code that takes the proxying36// mutex - which can once more reach this code in a nested call. To avoid37// interference between the two (there is just a single38// _emscripten_main_thread_futex at a time), unmark ourselves before calling39// the potentially-recursive call. See below for how we handle the case of our40// futex being notified during the time in between when we are not set as the41// value of _emscripten_main_thread_futex.42void* last_addr = a_cas_p(&_emscripten_main_thread_futex, 0, (void*)addr);43// We must not have already been waiting.44assert(last_addr == 0);4546while (1) {47// Check for a timeout.48now = emscripten_get_now();49if (now > end) {50// We timed out, so stop marking ourselves as waiting.51last_addr = a_cas_p(&_emscripten_main_thread_futex, (void*)addr, 0);52// The current value must have been our address which we set, or53// in a race it was set to 0 which means another thread just allowed54// us to run, but (tragically) that happened just a bit too late.55assert(last_addr == addr || last_addr == 0);56return -ETIMEDOUT;57}58// We are performing a blocking loop here, so we must handle proxied59// events from pthreads, to avoid deadlocks.60// Note that we have to do so carefully, as we may take a lock while61// doing so, which can recurse into this function; stop marking62// ourselves as waiting while we do so.63last_addr = a_cas_p(&_emscripten_main_thread_futex, (void*)addr, 0);64assert(last_addr == addr || last_addr == 0);65if (last_addr == 0) {66// We were told to stop waiting, so stop.67break;68}69_emscripten_yield(now);7071// Check the value, as if we were starting the futex all over again.72// This handles the following case:73//74// * wait on futex A75// * recurse into _emscripten_yield(),76// which waits on futex B. that sets the _emscripten_main_thread_futex77// address to futex B, and there is no longer any mention of futex A.78// * a worker is done with futex A. it checks _emscripten_main_thread_futex79// but does not see A, so it does nothing special for the main thread.80// * a worker is done with futex B. it flips mainThreadMutex from B81// to 0, ending the wait on futex B.82// * we return to the wait on futex A. _emscripten_main_thread_futex is 0,83// but that is because of futex B being done - we can't tell from84// _emscripten_main_thread_futex whether A is done or not. therefore,85// check the memory value of the futex.86//87// That case motivates the design here. Given that, checking the memory88// address is also necessary for other reasons: we unset and re-set our89// address in _emscripten_main_thread_futex around calls to90// _emscripten_yield(), and a worker could91// attempt to wake us up right before/after such times.92//93// Note that checking the memory value of the futex is valid to do: we94// could easily have been delayed (relative to the worker holding on95// to futex A), which means we could be starting all of our work at the96// later time when there is no need to block. The only "odd" thing is97// that we may have caused side effects in that "delay" time. But the98// only side effects we can have are to call99// _emscripten_yield(). That is always ok to100// do on the main thread (it's why it is ok for us to call it in the101// middle of this function, and elsewhere). So if we check the value102// here and return, it's the same is if what happened on the main thread103// was the same as calling _emscripten_yield()104// a few times before calling emscripten_futex_wait().105if (__c11_atomic_load((_Atomic uint32_t*)addr, __ATOMIC_SEQ_CST) != val) {106return -EWOULDBLOCK;107}108109// Mark us as waiting once more, and continue the loop.110last_addr = a_cas_p(&_emscripten_main_thread_futex, 0, (void*)addr);111assert(last_addr == 0);112}113return 0;114}115116int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms) {117if ((((intptr_t)addr)&3) != 0) {118return -EINVAL;119}120121// Pass 0 here, which means we don't have access to the current time in this122// function. This tells _emscripten_yield to call emscripten_get_now if (and123// only if) it needs to know the time.124_emscripten_yield(0);125126int ret;127emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_WAITFUTEX);128129// For the main browser thread and audio worklets we can't use130// __builtin_wasm_memory_atomic_wait32 so we have busy wait instead.131if (!_emscripten_thread_supports_atomics_wait()) {132ret = futex_wait_main_browser_thread(addr, val, max_wait_ms);133emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);134return ret;135}136137// -1 (or any negative number) means wait indefinitely.138int64_t max_wait_ns = -1;139if (max_wait_ms != INFINITY) {140max_wait_ns = (int64_t)(max_wait_ms*1000*1000);141}142#ifdef EMSCRIPTEN_DYNAMIC_LINKING143// After the main thread queues dlopen events, it checks if the target threads144// are sleeping.145// If `sleeping` is set then the main thread knows that event will be146// processed after the sleep (before any other user code). In this case the147// main thread does not wait for any kind of response form the thread.148// If `sleeping` is not set then we know we should wait for the thread process149// the queue, either from the call here directly after setting `sleeping` to150// 1, or from another callsite (e.g. the one in `emscripten_yield`).151int is_runtime_thread = emscripten_is_main_runtime_thread();152if (!is_runtime_thread) {153__pthread_self()->sleeping = 1;154_emscripten_process_dlopen_queue();155}156#endif157ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);158#ifdef EMSCRIPTEN_DYNAMIC_LINKING159if (!is_runtime_thread) {160__pthread_self()->sleeping = 0;161_emscripten_process_dlopen_queue();162}163#endif164165done:166emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);167168// memory.atomic.wait32 returns:169// 0 => "ok", woken by another agent.170// 1 => "not-equal", loaded value != expected value171// 2 => "timed-out", the timeout expired172if (ret == 1) {173return -EWOULDBLOCK;174}175if (ret == 2) {176return -ETIMEDOUT;177}178assert(ret == 0);179return 0;180}181182183