-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[wasm-mt] Support async JS interop on threadpool threads #84494
Changes from 1 commit
8c3fef3
a3db886
847ecd5
dd69c8f
451d21b
2889a1f
92d80a5
5f4acc8
806b770
d2811f7
48d22ae
c813701
0a4dd8c
77a54ff
fd99953
4671e03
e22ca46
c591501
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1088,9 +1088,7 @@ typedef struct { | |
MonoThreadStart start_func; | ||
gpointer start_func_arg; | ||
gboolean force_attach; | ||
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) | ||
gboolean returns_to_js_event_loop; | ||
#endif | ||
gboolean external_eventloop; | ||
gboolean failed; | ||
MonoCoopSem registered; | ||
} StartInfo; | ||
|
@@ -1176,9 +1174,7 @@ start_wrapper_internal (StartInfo *start_info, gsize *stack_ptr) | |
/* Let the thread that called Start() know we're ready */ | ||
mono_coop_sem_post (&start_info->registered); | ||
|
||
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) | ||
gboolean returns_to_js_event_loop = start_info->returns_to_js_event_loop; | ||
#endif | ||
gboolean external_eventloop = start_info->external_eventloop; | ||
|
||
if (mono_atomic_dec_i32 (&start_info->ref) == 0) { | ||
mono_coop_sem_destroy (&start_info->registered); | ||
|
@@ -1247,13 +1243,11 @@ start_wrapper_internal (StartInfo *start_info, gsize *stack_ptr) | |
|
||
THREAD_DEBUG (g_message ("%s: (%" G_GSIZE_FORMAT ") Start wrapper terminating", __func__, mono_native_thread_id_get ())); | ||
|
||
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) | ||
if (returns_to_js_event_loop) { | ||
/* if the thread wants to stay alive, don't clean up after it */ | ||
if (emscripten_runtime_keepalive_check()) | ||
if (G_UNLIKELY (external_eventloop)) { | ||
/* if the thread wants to stay alive in an external eventloop, don't clean up after it */ | ||
if (mono_thread_platform_external_eventloop_keepalive_check ()) | ||
return 0; | ||
} | ||
#endif | ||
|
||
/* Do any cleanup needed for apartment state. This | ||
* cannot be done in mono_thread_detach_internal since | ||
|
@@ -1281,19 +1275,15 @@ start_wrapper (gpointer data) | |
info = mono_thread_info_attach (); | ||
info->runtime_thread = TRUE; | ||
|
||
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) | ||
gboolean returns_to_js_event_loop = start_info->returns_to_js_event_loop; | ||
#endif | ||
gboolean external_eventloop = start_info->external_eventloop; | ||
/* Run the actual main function of the thread */ | ||
res = start_wrapper_internal (start_info, (gsize*)info->stack_end); | ||
|
||
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) | ||
if (returns_to_js_event_loop) { | ||
if (G_UNLIKELY (external_eventloop)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How will this thread be used after it has returned from its thread function? Will we use it to run other code (JS event loop), "impersonating" the thread, like having events that should run managed code, referencing this thread, so when picked up in JS even loop, it will still be able to complete those events, even if the original thread execution has stopped? Is that the background why we need keeping the managed thread alive as done in this PR? If so, will the thread move over to safe mode since it will still be attached to runtime, but not able to react to any suspend requests etc? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, exactly right. In order for JS promises to resolve, the JS eventloop must get a chance to run. So if we create a worker thread with The earlier PRs (see the PR description) refactored the managed threadpool worker code to use callbacks that are registered with the JS event loop in order to signal a threadpool worker when it has work available. The worker stays alive as long as there are unsettled JS promises (that are awaited by managed code) and then either exits (normal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes it should be in GC Safe when it's in the JS loop (I'll do a checked build this afternoon to verify. but I'm pretty sure that actually happens as part of the normal thread state management for the managed thread start function invocation. when it returns we will go to gc safe) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So then we keep a reference count or similar making sure the managed thread gets detached when there are no more outstanding references to the thread, correct? Sounded like we depend on TLS destructors to do that, so does code reacting to callback from JS eventloop, invoke managed code and then check refcount and if that is 0, close native thread, that in turn will trigger TLS destructors, detaching thread from runtime? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a gc safe assertion when we're exiting to the external event loop |
||
/* if the thread wants to stay alive, don't clean up after it */ | ||
if (emscripten_runtime_keepalive_check()) | ||
if (mono_thread_platform_external_eventloop_keepalive_check ()) | ||
return 0; | ||
} | ||
#endif | ||
|
||
mono_thread_info_exit (res); | ||
|
||
|
@@ -1381,9 +1371,7 @@ create_thread (MonoThread *thread, MonoInternalThread *internal, MonoThreadStart | |
start_info->start_func_arg = start_func_arg; | ||
start_info->force_attach = flags & MONO_THREAD_CREATE_FLAGS_FORCE_CREATE; | ||
start_info->failed = FALSE; | ||
#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) | ||
start_info->returns_to_js_event_loop = (flags & MONO_THREAD_CREATE_FLAGS_RETURNS_TO_JS_EVENT_LOOP) != 0; | ||
#endif | ||
start_info->external_eventloop = (flags & MONO_THREAD_CREATE_FLAGS_EXTERNAL_EVENTLOOP) != 0; | ||
mono_coop_sem_init (&start_info->registered, 0); | ||
|
||
if (flags != MONO_THREAD_CREATE_FLAGS_SMALL_STACK) | ||
|
@@ -4947,7 +4935,7 @@ ves_icall_System_Threading_Thread_StartInternal (MonoThreadObjectHandle thread_h | |
// HACK: threadpool threads can return to the JS event loop | ||
// WISH: support this for other threads, too | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be TODO or FIXME instead of wish so it's easier to find in codebase audits. I assume we're going to get to it eventually either way though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should probably just add a managed |
||
if (internal->threadpool_thread) | ||
create_flags |= MONO_THREAD_CREATE_FLAGS_RETURNS_TO_JS_EVENT_LOOP; | ||
create_flags |= MONO_THREAD_CREATE_FLAGS_EXTERNAL_EVENTLOOP; | ||
#endif | ||
|
||
res = create_thread (internal, internal, NULL, NULL, stack_size, create_flags, error); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the G_UNLIKELY here important or necessary? Just curious
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither - this code isn't particularly hot (I think), so it's more for documentation than anything else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is also a hint to compilers that supports it to put the branch out of line assisting the branch predictors heuristics, so it should then end up with a conditional jmp to an out of line code block when true, but branch predictor will predict it to not be taken (normal default heuristics for forward branches)., potentially improving execution speed around that block.