| /* |
| * Copyright 2023 The Emscripten Authors. All rights reserved. |
| * Emscripten is available under two separate licenses, the MIT license and the |
| * University of Illinois/NCSA Open Source License. Both these licenses can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <assert.h> |
| #include <math.h> |
| #include <pthread.h> |
| #include <stdatomic.h> |
| |
| #include "em_task_queue.h" |
| #include "pthread_impl.h" |
| #include "thread_mailbox.h" |
| #include "threading_internal.h" |
| |
| int emscripten_thread_mailbox_ref(pthread_t thread) { |
| // Attempt to increment the refcount, being careful not to increment it if we |
| // ever observe a 0. |
| int prev_count = thread->mailbox_refcount; |
| while (1) { |
| if (prev_count == 0) { |
| // The mailbox is already closed! |
| return 0; |
| } |
| int desired_count = prev_count + 1; |
| if (atomic_compare_exchange_weak( |
| &thread->mailbox_refcount, &prev_count, desired_count)) { |
| return 1; |
| } |
| } |
| } |
| |
| // Decrement and return the refcount. |
| void emscripten_thread_mailbox_unref(pthread_t thread) { |
| int new_count = atomic_fetch_sub(&thread->mailbox_refcount, 1) - 1; |
| assert(new_count >= 0); |
| if (new_count == 0) { |
| // The count is now zero. The thread that owns this queue may be waiting to |
| // shut down. Notify the thread that it is safe to proceed now that the |
| // mailbox is closed. |
| emscripten_futex_wake(&thread->mailbox_refcount, INT_MAX); |
| } |
| } |
| |
| void _emscripten_thread_mailbox_shutdown(pthread_t thread) { |
| assert(thread == pthread_self()); |
| |
| // Decrement the refcount and wait for it to reach zero. |
| assert(thread->mailbox_refcount > 0); |
| int count = atomic_fetch_sub(&thread->mailbox_refcount, 1) - 1; |
| |
| while (count != 0) { |
| emscripten_futex_wait(&thread->mailbox_refcount, count, INFINITY); |
| count = thread->mailbox_refcount; |
| } |
| |
| // The mailbox is now closed. No more messages will be enqueued. Run the |
| // shutdown handler for any message already in the queue. |
| em_task_queue_cancel(thread->mailbox); |
| |
| // The mailbox is now empty and will not be accessed again after this point. |
| em_task_queue_destroy(thread->mailbox); |
| } |
| |
| void _emscripten_thread_mailbox_init(pthread_t thread) { |
| thread->mailbox = em_task_queue_create(thread); |
| thread->mailbox_refcount = 1; |
| thread->waiting_async = 0; |
| } |
| |
| // Internal function, called from runtime_pthread.js |
| void _emscripten_check_mailbox() { |
| // Before we attempt to execute a request from another thread make sure we |
| // are in sync with all the loaded code. |
| // For example, in PROXY_TO_PTHREAD the atexit functions are called via |
| // a proxied call, and without this call to synchronize we would crash if |
| // any atexit functions were registered from a side module. |
| DBG("_emscripten_check_mailbox"); |
| assert(pthread_self()); |
| em_task_queue* mailbox = pthread_self()->mailbox; |
| mailbox->notification = NOTIFICATION_RECEIVED; |
| em_task_queue_execute(pthread_self()->mailbox); |
| notification_state expected = NOTIFICATION_RECEIVED; |
| atomic_compare_exchange_strong( |
| &mailbox->notification, &expected, NOTIFICATION_NONE); |
| // After every mailbox check we call `__pthread_testcancel` in case |
| // one of the proxied functions was from pthread_kill(SIGCANCEL). |
| __pthread_testcancel(); |
| } |
| |
| void emscripten_thread_mailbox_send(pthread_t thread, task t) { |
| assert(thread->mailbox_refcount > 0); |
| |
| pthread_mutex_lock(&thread->mailbox->mutex); |
| if (!em_task_queue_enqueue(thread->mailbox, t)) { |
| assert(0 && "No way to correctly recover from allocation failure"); |
| } |
| pthread_mutex_unlock(&thread->mailbox->mutex); |
| |
| // If there is no pending notification for this mailbox, create one. If an old |
| // notification is currently being processed, it may or may not execute the |
| // new work. In case it does not, the new notification will ensure the work is |
| // still executed. |
| notification_state previous = |
| atomic_exchange(&thread->mailbox->notification, NOTIFICATION_PENDING); |
| if (previous != NOTIFICATION_PENDING) { |
| if (thread->waiting_async) { |
| __builtin_wasm_memory_atomic_notify((int*)thread, -1); |
| } else { |
| _emscripten_notify_mailbox_postmessage(thread, pthread_self()); |
| } |
| } |
| } |