-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
keeping node alive while workers are working #23092
Comments
ccing people from that PR: @kleisauke @sbc100 |
Ah, this is indeed due to PR #19073. Calling the internal How about exposing this function in Emscripten's public API? That way, you could do: --- a/main.cc
+++ b/main.cc
@@ -2,14 +2,11 @@
#include <thread>
#include <stdio.h>
#include <emscripten.h>
+#include <emscripten/threading.h>
extern "C" void spawn() {
printf("spawning\n");
std::thread t([] {
- MAIN_THREAD_ASYNC_EM_ASM({
- clearTimeout(spawnTimeout);
- });
-
printf("spawned\n");
double v = 1.0;
@@ -21,8 +18,6 @@ extern "C" void spawn() {
resolve($0);
}, v);
});
+ emscripten_thread_set_strongref(t.native_handle());
t.detach();
- EM_ASM({
- spawnTimeout = setTimeout(() => {}, 5000);
- });
} I just opened PR #23152 for this. |
Nice! Is there an easy way to try the PR locally? I'd be happy to test it for my use case if so. |
@bakkot To test that PR with the tip-of-tree compilers, you could use: $ cd emsdk/
$ ./emsdk install tot emscripten-main-64bit --override-repository emscripten-main-64bit@https://github.com/kleisauke/emscripten/tree/node-strong-ref-helper-public
$ ./emsdk activate tot emscripten-main-64bit
$ source ./emsdk_env.sh
# Verify commit
$ git -C emscripten/main rev-parse HEAD
cfe6160f56273902cf9e52f875ce4083d967c35a
# Invalidate cache
$ emcc --clear-cache Alternatively, you can test that private function using: --- a/main.cc
+++ b/main.cc
@@ -3,13 +3,11 @@
#include <stdio.h>
#include <emscripten.h>
+extern "C" void _emscripten_thread_set_strongref(pthread_t thread);
+
extern "C" void spawn() {
printf("spawning\n");
std::thread t([] {
- MAIN_THREAD_ASYNC_EM_ASM({
- clearTimeout(spawnTimeout);
- });
-
printf("spawned\n");
double v = 1.0;
@@ -21,8 +19,6 @@ extern "C" void spawn() {
resolve($0);
}, v);
});
+ _emscripten_thread_set_strongref(t.native_handle());
t.detach();
- EM_ASM({
- spawnTimeout = setTimeout(() => {}, 5000);
- });
} However, this causes Node.js to hang indefinitely due the emscripten/src/library_pthread.js Line 191 in 1092ec3
(i.e. it doesn't .unref() the worker when it's finished)
To fix this, you can temporarily remove that conditional in |
I would hope we could make fix this without exposing a custom API like that, but maybe there is no other option. Also I wonder if that API should be in the form a |
Also, it makes me wonder what the corresponding technique would be in the native world. If you return from |
Also, can you explain a little more why don't want to use the It seems that you could store the result of You say "then if the worker crashes or is killed (as sometimes happens) it will again need to wait for the timeout." couldn't you clear the timeout in those cases? Also if there is a possiblity that the thread dies or is killed wouldn't you want a settimeout to fire at some point so you can report that error (e.g. "thread unresponsive!") |
It seems fairly common to use setTimeout / setInterval to keep a node process alive longer than it normally would be: https://stackoverflow.com/questions/23622051/how-to-forcibly-keep-a-node-js-process-from-terminating |
Because then the timer won't get cleared if the thread dies before resolving. And in my application the timer needs to be extremely long, because the work done in the thread may take a very long time (hours).
If there's an easy way to do that, sure, that would work too. I didn't see one offhand.
I have a have a |
But that setInterval can go on a long a you want.. forever if you want. How does your application detect the thread "dying"?
If you have a setInterval or setTimeout going on the main thread then the You would do something like this:
You would also write the same thing in C/C++ using |
I'm fine with having a long timer. I just don't want to have a long timer if it's possible that no work is actually happening, which can happen if the worker thread exits without clearing the timer.
The
... Right, which is part of why I'm trying to avoid a I am trying to find a solution that does not require me to have a long-running Also, timers are a bit of a hack here. Workers which are doing work you care about are supposed to be strongly ref'd in order to keep the main thread from exiting. |
While this is true for node Workers its not true for pthreads and POSIX. When then main thread exits here the semantics are that it brings the whole application down, including any threads that might be doing work. So we have conflicting requirements. Perhaps using |
I would hope we can find a better way to detect the thread dying. I assume we are talking about the pthread exiting of its own accord and the worker being returned to pool? Assuming you want stick with pthreads, one way to do would be call |
Sure, but the main thread in this case is in node, not POSIX; I'm just calling into emscripten's output as a library. And it isn't really intentionally exiting the main thread: the code on that thread is in the middle of an
I'm currently relying on
It's awkward to retrofit that into the C++ threads I'm currently using, but I could see if I can make |
How about this solution: We already have a mechanism for signally that the main thread is not actually dead and should the runtime should be kept going. We have How about if said: active pthreads are not enough to the runtime alive on their own, but if the runtime is otherwise being kept alive then they are? In other words which should more accurately reflect that fact "then the main thread exits, pthreads should be killed". Then all you would need is add |
What do you mean by "main function"? I'm writing a library, not an application. The library is intended to be consumed by JavaScript programmers. The interface for users of the library is that they call async functions exported by the library, and when those functions finish doing work the promise they returned settles with the result of the call just like any other async function, and this doesn't block the main thread. And also when their program finishes node exits. I don't want to tell users of this library that they have to call some other function sometimes. Users ideally should not have to know that the library is using emscripten at all. |
If you don't have a main function and you are writing a library then your runtime should stay alive until you explicitly call exit(). In this configuration I think all pthread should also keep the program alive until the call to exit(). |
In other words, I think all your pthreads should be strong referenced. I think we can figure out how to make that happen without exposing a new API. |
What call to |
In that case the runtime would never exit, yes. |
In the library configuration I think its reasonable for pthreads to keep node alive. |
Assuming you mean pthreads which are actively doing work and not just workers in the pool, that sounds good to me. |
Yes, we are only talking about "active" threads here. Inactive workers in the pool should never keep anything alive. |
Version of emscripten/emsdk:
Failing command line in full:
I have a library which is intended to be driven from JS (i.e., no main loop in C, and using
-s MODULARIZE=1
).I'm trying to spawn a thread, do some work, and then get the result back in the main thread. I have the following three bits of code, with the first two being the inputs to emcc and the third being the driver:
Running
node run.mjs
printsand then exits, without printing the sum or giving me the result.
I was really hoping it would instead do the work and give me the result. That worked prior to #19073 (or to be more precise, it worked in version 3.1.15; I'm assuming that PR is the relevant change). I assume this is because node exits when the main thread has no tasks queued and the workers are all
unref
'd.If I comment out the "clearTimeout" line it works, but then of course I need to wait for the timeout. I can move the
clearTimeout
later, but then if the worker crashes or is killed (as sometimes happens) it will again need to wait for the timeout.Is there some way I can get it to stay alive? Maybe keep the workers
.ref
'd while they're actually doing work?The text was updated successfully, but these errors were encountered: