diff --git a/AUTHORS b/AUTHORS index fdabaff7970e5..be57ca1c61c88 100644 --- a/AUTHORS +++ b/AUTHORS @@ -562,3 +562,4 @@ a license to everyone to use it as detailed in LICENSE.) * Érico Porto * Albert Vaca Cintora * Zhi An Ng (copyright owned by Google, Inc.) +* Luigi F. Cruz diff --git a/system/include/emscripten/threading.h b/system/include/emscripten/threading.h index 48949750c9a28..6d334baa16ad4 100644 --- a/system/include/emscripten/threading.h +++ b/system/include/emscripten/threading.h @@ -268,6 +268,10 @@ int _emscripten_call_on_thread(int force_async, pthread_t target_thread, EM_FUNC // but may be simpler to reason about in some cases. #define emscripten_dispatch_to_thread_async(target_thread, sig, func_ptr, satellite, ...) _emscripten_call_on_thread(1, (target_thread), (sig), (void*)(func_ptr), (satellite),##__VA_ARGS__) +// Similar to emscripten_dispatch_to_thread, but always runs the +// function synchronously, even if on the same thread. +#define emscripten_dispatch_to_thread_sync(target_thread, sig, func_ptr, satellite, ...) _emscripten_call_on_thread(2, (target_thread), (sig), (void*)(func_ptr), (satellite),##__VA_ARGS__) + // Returns 1 if the current thread is the thread that hosts the Emscripten runtime. int emscripten_is_main_runtime_thread(void); diff --git a/system/lib/pthread/library_pthread.c b/system/lib/pthread/library_pthread.c index db6b910c6e0d1..53d506e1630ad 100644 --- a/system/lib/pthread/library_pthread.c +++ b/system/lib/pthread/library_pthread.c @@ -454,7 +454,7 @@ pthread_t emscripten_main_browser_thread_id() { return main_browser_thread_id_; } -int _emscripten_do_dispatch_to_thread(pthread_t target_thread, em_queued_call* call) { +int _emscripten_do_dispatch_to_thread_global(pthread_t target_thread, em_queued_call* call, int drop) { assert(call); // #if PTHREADS_DEBUG // TODO: Create a debug version of pthreads library @@ -492,7 +492,7 @@ int _emscripten_do_dispatch_to_thread(pthread_t target_thread, em_queued_call* c // If queue of the main browser thread is full, then we wait. (never drop messages for the main // browser thread) - if (target_thread == emscripten_main_browser_thread_id()) { + if (target_thread == emscripten_main_browser_thread_id() || drop == 0) { emscripten_futex_wait((void*)&q->call_queue_head, head, INFINITY); pthread_mutex_lock(&call_queue_lock); head = q->call_queue_head; @@ -529,6 +529,11 @@ int _emscripten_do_dispatch_to_thread(pthread_t target_thread, em_queued_call* c return 0; } +int _emscripten_do_dispatch_to_thread( + pthread_t target_thread, em_queued_call* call) { + return _emscripten_do_dispatch_to_thread_global(target_thread, call, 1); +} + void emscripten_async_run_in_main_thread(em_queued_call* call) { _emscripten_do_dispatch_to_thread(emscripten_main_browser_thread_id(), call); } @@ -859,8 +864,11 @@ em_queued_call* emscripten_async_waitable_run_in_main_runtime_thread_( } int _emscripten_call_on_thread( - int forceAsync, + int dispatchMode, pthread_t targetThread, EM_FUNC_SIGNATURE sig, void* func_ptr, void* satellite, ...) { + // dispatchMode==0 Synchronous if in the same thread_id, otherwise asynchronous. + // dispatchMode==1 Force asynchronous call even if in the same thread_id. + // dispatchMode==2 Always force a synchronous call. int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig); em_queued_call* q = em_queued_call_malloc(); assert(q); @@ -905,13 +913,20 @@ int _emscripten_call_on_thread( q->calleeDelete = 1; // The called function will not be async if we are on the same thread; force // async if the user asked for that. - if (forceAsync) { + if (dispatchMode == 1) { EM_ASM({ setTimeout(function() { __emscripten_do_dispatch_to_thread($0, $1); }, 0); }, targetThread, q); return 0; + } else if (dispatchMode == 2) { + q->calleeDelete = 0; + _emscripten_do_dispatch_to_thread_global(targetThread, q, 0); + emscripten_wait_for_call_v(q, INFINITY); + int res = q->returnValue.i; + em_queued_call_free(q); + return res; } else { return _emscripten_do_dispatch_to_thread(targetThread, q); } diff --git a/tests/other/metadce/minimal_Oz_USE_PTHREADS_PROXY_TO_PTHREAD.funcs b/tests/other/metadce/minimal_Oz_USE_PTHREADS_PROXY_TO_PTHREAD.funcs index 17dedb5a5c6cd..c55297683aa73 100644 --- a/tests/other/metadce/minimal_Oz_USE_PTHREADS_PROXY_TO_PTHREAD.funcs +++ b/tests/other/metadce/minimal_Oz_USE_PTHREADS_PROXY_TO_PTHREAD.funcs @@ -12,6 +12,7 @@ $__wasm_init_memory $_do_call $_emscripten_call_on_thread $_emscripten_do_dispatch_to_thread +$_emscripten_do_dispatch_to_thread_global $_emscripten_thread_init $_main_thread $a_cas @@ -32,6 +33,7 @@ $emscripten_stack_set_limits $emscripten_sync_run_in_main_thread $emscripten_sync_run_in_main_thread $emscripten_tls_init +$emscripten_wait_for_call_v $free_tls $init_mparams $sbrk diff --git a/tests/pthread/call_sync.c b/tests/pthread/call_sync.c new file mode 100644 index 0000000000000..6527c77baa578 --- /dev/null +++ b/tests/pthread/call_sync.c @@ -0,0 +1,39 @@ +/* + * Copyright 2020 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 +#include +#include +#include +#include +#include + +static _Atomic bool started = false; + +void *new_thread(void* ctx) { + started = true; + emscripten_exit_with_live_runtime(); + return NULL; +} + +int magic_number() { + assert(emscripten_main_browser_thread_id() != pthread_self()); + return 42; +} + +int main() { + pthread_t worker; + assert(pthread_create(&worker, NULL, new_thread, NULL) == 0); + while (!started) + emscripten_sleep(100); + + assert(emscripten_dispatch_to_thread_sync(worker, EM_FUNC_SIG_I, &magic_number, NULL) == 42); + +#ifdef REPORT_RESULT + REPORT_RESULT(1); +#endif +} diff --git a/tests/test_browser.py b/tests/test_browser.py index 02050a82f3f14..c17c2f8fcf7a3 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -4011,6 +4011,10 @@ def test_pthread_run_on_main_thread_flood(self): def test_pthread_call_async(self): self.btest(test_file('pthread', 'call_async.c'), expected='1', args=['-s', 'USE_PTHREADS']) + @requires_threads + def test_pthread_call_sync(self): + self.btest(path_from_root('tests', 'pthread', 'call_sync.c'), expected='1', args=['-s', 'ASYNCIFY', '-s', 'USE_PTHREADS']) + # Test that it is possible to synchronously call a JavaScript function on the main thread and get a return value back. @requires_threads def test_pthread_call_sync_on_main_thread(self):