-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
Current AsyncWorker scheduling conflict with threads used by other libraries #8
Comments
Offtop: it was pretty funny how I found this library. I was actually googling for exactly same-named library of mine from 2017 - https://github.com/RReverser/emnapi - as I wanted to add some more functions to it, but instead found your version of |
@RReverser Now I understand the scenarios where problems can arise, but I still have questions on the following points:
Do you mean emnapi should implement the worker scheduling mechanism and not depend on Emscripten pthreads?
|
So the purpose of |
I did some work on feat-worker-pool branch, tested locally, works fine. Suggestion or PR welcome. |
Thank you! You have no idea how much this feedback means to me. Every time surprised someone actually reads those 😅
Yes and yes. That is, it should be a subset of PTHREAD_POOL_SIZE that is exclusively allocated and used only by emnapi and no one else to prevent deadlocks.
I actually won't be near the computer today, if you don't mind, I'll take a look on Monday. Thanks for the quick response! |
After b8535be, Currently I have no idea how to implement async work without relying on pthreads. (emscripten-core/emscripten#17213)
Looking forward to your code review and testing. Have a nice weekend. |
Yeah at least with That said, I'm still concerned about "piggy-backing" on the PThread object for couple of reasons:
I think a safer and more efficient option would be have your own worker pool on the C/C++ side that uses the public At the very least, as an intermediate option to fix just the issue (2) from the above list, you could override |
Great thanks for your advice. I have ported libuv's thread pool implementation. It looks perfect! |
Currently, AsyncWorker uses the global pthread pool for scheduling its own tasks, taking threads as they become available. This works fine if AsyncWorker is the exclusive user of multithreading, but not when the addon is linked with other libraries that also rely on threads.
Here's the general problematic scenario:
A
hasasync_method
that spawns tasks onto anAsyncWorker
B
and invokesB.some_method()
that uses up toN
threads itselfPTHREAD_POOL_SIZE=N+1
(N
threads for the library + 1 for the async op in the addon itself)Now, if user invokes
A.async_method()
, the addonA
will take one thread from the pool for the worker,N
threads are left, the library takes thoseN
threads, finishes the work, everything's fine.But now another user has code that invokes
async_method()
on various inputs in parallel e.g. viaPromise.all
:Now, the addon
A
will take 2 threads (one per eachasync_method()
invocation) to create theAsyncWorker
s, each of those workers invokes theB.some_method()
, each of those parallelB.some_method()
invocations tries to allocateN
workers ==2*N
total, but the pool only has(N+1)-2 == N-1
threads at this point! So each of them deadlocks.User could increase the
PTHREAD_POOL_SIZE
to2*(N+1)
, but then the same failure will happen if they happen to invoke 3 invocations of the library in parallel, and so on.I actually observed this behaviour with deadlocks in a real-world addon I'm cross-compiling to Wasm with emnapi right now, and it took some time to figure out what's happening.
In my case I fixed this by monkey-patching the async methods to queue them one by one when compiled to Wasm, but, ideally, this should be handled at the emnapi level.
I think the only reliable solution here is for emnapi to have a separate, exclusive, thread pool, instead of taking as many threads as it wants from the global Emscripten pool. Then, it could either use the same JS logic or even some C++ implementation of work-stealing to execute max of
EMNAPI_WORKER_POOL_SIZE
in parallel, and no more. This would allow users to configureEMNAPI_WORKER_POOL_SIZE
to the desired number of tasks, andPTHREAD_POOL_SIZE
toEMNAPI_WORKER_POOL_SIZE * (N + 1)
in advance, and avoid risk of deadlocks altogether.And then, those users who know that they're not linking against any multithreaded libraries will be able to set both
PTHREAD_POOL_SIZE
andEMNAPI_WORKER_POOL
to(number of CPU cores)
and get max parallelisation as they can today.The text was updated successfully, but these errors were encountered: