Skip to content
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

Conversation

lambdageek
Copy link
Member

This is Part 2 of #84489 - landing support for async JS interop on threadpool threads in multi-threaded WebAssembly.

This PR adds two things:

  1. A new unmanaged 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.
  2. A new flavor of the managed LowLevelLifoSemaphore that is not allowed to use the normal Wait() function, and instead needs to use the callback-based PrepareAsyncWait() function.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Apr 7, 2023
@ghost ghost assigned lambdageek Apr 7, 2023
@lambdageek lambdageek added arch-wasm WebAssembly architecture area-VM-threading-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Apr 7, 2023
@ghost
Copy link

ghost commented Apr 7, 2023

Tagging subscribers to 'arch-wasm': @lewing
See info in area-owners.md if you want to be subscribed.

Issue Details

This is Part 2 of #84489 - landing support for async JS interop on threadpool threads in multi-threaded WebAssembly.

This PR adds two things:

  1. A new unmanaged 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.
  2. A new flavor of the managed LowLevelLifoSemaphore that is not allowed to use the normal Wait() function, and instead needs to use the callback-based PrepareAsyncWait() function.
Author: lambdageek
Assignees: lambdageek
Labels:

arch-wasm, area-VM-threading-mono, needs-area-label

Milestone: -

@lambdageek lambdageek force-pushed the pieces-wasm-threadpool-worker-2-async-lifo-semaphore branch 2 times, most recently from 98bd2ec to 40fb41c Compare April 8, 2023 05:17
@lambdageek

This comment was marked as resolved.

@lambdageek
Copy link
Member Author

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?

@jkoritzinsky
Copy link
Member

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.

Copy link
Member

@kouvel kouvel left a 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.

src/mono/mono/utils/lifo-semaphore.c Outdated Show resolved Hide resolved
{
GCHandle gchandle = GCHandle.FromIntPtr(userData);
WaitEntry internalWaitEntry = (WaitEntry)gchandle.Target!;
gchandle.Free();
Copy link
Member

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?

Copy link
Member Author

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.

@@ -16,7 +24,7 @@ void
mono_lifo_semaphore_delete (LifoSemaphore *semaphore)
{
g_assert (semaphore->head == NULL);
Copy link
Member

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?

Copy link
Member Author

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)

@lambdageek lambdageek force-pushed the pieces-wasm-threadpool-worker-2-async-lifo-semaphore branch from b6ba5a1 to 9d94720 Compare April 19, 2023 13:59
@lambdageek
Copy link
Member Author

WBT failures are due to #85010

@lambdageek lambdageek force-pushed the pieces-wasm-threadpool-worker-2-async-lifo-semaphore branch from 95fdf49 to 36217dd Compare April 19, 2023 17:28
Copy link
Member

@kouvel kouvel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@lambdageek
Copy link
Member Author

@kg @kouvel could I get a sign-off

lambdageek and others added 11 commits April 20, 2023 11:08
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
@lambdageek lambdageek force-pushed the pieces-wasm-threadpool-worker-2-async-lifo-semaphore branch from 36217dd to 0a9d8f7 Compare April 20, 2023 15:20
@lambdageek
Copy link
Member Author

Got an ok from @kg offline.

@lambdageek lambdageek merged commit dcb34de into dotnet:main Apr 21, 2023
@ghost ghost locked as resolved and limited conversation to collaborators May 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-wasm WebAssembly architecture area-VM-threading-mono
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants