-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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] A version of LowLevelLifoSemaphore that uses callbacks on the browser #84491
[wasm-mt] A version of LowLevelLifoSemaphore that uses callbacks on the browser #84491
Conversation
Tagging subscribers to 'arch-wasm': @lewing Issue DetailsThis is Part 2 of #84489 - landing support for async JS interop on threadpool threads in multi-threaded WebAssembly. This PR adds two things:
|
src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
Outdated
Show resolved
Hide resolved
98bd2ec
to
40fb41c
Compare
This comment was marked as resolved.
This comment was marked as resolved.
Not sure who should look at the native C bits here. @jkoritzinsky is it something you could take a look at? @kg, @pavelsavara, @lateralusX maybe you also? |
I don't have a lot of familiarity with this portion of the Mono code base. I can take a look, but someone from Mono that works on WASM should definitely be the main reviewer for that component. |
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.
Just one question, otherwise LGTM. I only did a quick scan over the native parts, probably would be good to have someone familiar with WASM review as well.
....Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs
Outdated
Show resolved
Hide resolved
....Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs
Outdated
Show resolved
Hide resolved
....Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs
Outdated
Show resolved
Hide resolved
{ | ||
GCHandle gchandle = GCHandle.FromIntPtr(userData); | ||
WaitEntry internalWaitEntry = (WaitEntry)gchandle.Target!; | ||
gchandle.Free(); |
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.
According to the sample in https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.gchandle.fromintptr?view=net-8.0 I'm not sure it's correct to free the handle here. I think it might be a double-free, unless the caller isn't freeing their handle?
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.
Yea this is different from the example in the docs. In this case the ownership of the GC handle passes from PrepareAsyncWaitCore
- which creates the GCHandle, to the underlying native wait structure, to the SuccessCallback
or TimeoutCallback
. At that point, the callback is the sole owner of the GC Handle (the underlying native wait entry was already freed). So the callback is responsible for freeing the GCHandle.
In the example in the docs, the call to EnumWindows never owns the GCHandle - it is always owned by the calling function, which is responsible for freeing it.
src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs
Outdated
Show resolved
Hide resolved
@@ -16,7 +24,7 @@ void | |||
mono_lifo_semaphore_delete (LifoSemaphore *semaphore) | |||
{ | |||
g_assert (semaphore->head == NULL); |
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.
does this need to occur inside the lock?
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.
I don't think so. This gets called from DeleteInternal
which is, in turn, called from IDisposable.Dispose
for the managed object. So at that point we don't expect contention on the semaphore because there should be nothing in managed code that keeps the semaphore alive.
(The reality is I think it's probably difficult to call Dispose
correctly in managed - except maybe from some finalizer. For the threadpool in particular, i'm pretty sure it's never actually called at all. The semaphore lives for the lifetime of the runtime)
075964a
to
b5cd9d8
Compare
src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
Outdated
Show resolved
Hide resolved
b6ba5a1
to
9d94720
Compare
WBT failures are due to #85010 |
....Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs
Outdated
Show resolved
Hide resolved
95fdf49
to
36217dd
Compare
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.
LGTM, thanks!
Add a new kind of LifoSemaphore that has a callback-based wait function, instead of a blocking wait using Emscripten's ability to send work from one webworker to another in C. This will allow us to wait for a semaphore from the JS event loop in a web worker.
A normal LowLevelLifoSemaphore that can do a synchronous wait and another that can do a callback-based wait from the JS event loop
Move the counts to the base class Move Release to the base class, make ReleaseCore abstract
instead of magic constants that are otherwise not needed in managed
…areExchange When a thread wakes after waiting for a semaphore to be released, if it raced with another thread that is also trying to update the semaphore counts and loses, it has to go back to waiting again. In that case, decrement the remaining timeout by the elapsed wait time so that the next wait is shorter.
make PrepareAsyncWaitCore static and remove a redundant argument
36217dd
to
0a9d8f7
Compare
Got an ok from @kg offline. |
This is Part 2 of #84489 - landing support for async JS interop on threadpool threads in multi-threaded WebAssembly.
This PR adds two things:
LifoSemaphoreAsyncWait
semaphore that can use Emscripten's ability to push C calls from one thread to another in order to implement a callback-based semaphore - when a thread wants to wait, it sets up a success callback and a timeout callback, and then can return to the JS event loop. When the semaphore is released, Emscripten will trigger the callback to run on the waiting thread. If the wait times out, the timeout callback will run.LowLevelLifoSemaphore
that is not allowed to use the normalWait()
function, and instead needs to use the callback-basedPrepareAsyncWait()
function.