Skip to content

Commit c718af3

Browse files
committed
Fix Node.js exit for library builds with pthreads
When building without a main function (a library build), and using pthreads to do background work, the main Node.js thread has no event loop work to keep Node.js alive. The workers are also unreferenced when they start executing, so Node.js will exit prematurely before the background thread can finish its work. To prevent this, change the worker reference logic so that we only `unref()` workers upon execution if `HAS_MAIN` is enabled. For library builds, we keep them referenced (`worker.ref()`) to keep the process alive while they are active. We also ensure idle workers returned to the pool are `unref()`'d across all Node builds (not just with `PROXY_TO_PTHREAD`) so that an idle pool doesn't leak references preventing exit. Fixes #23092
1 parent 3051725 commit c718af3

5 files changed

Lines changed: 71 additions & 3 deletions

File tree

src/lib/libpthread.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,11 @@ var LibraryPThread = {
208208
// worker pool as an unused worker.
209209
worker.pthread_ptr = 0;
210210

211-
#if ENVIRONMENT_MAY_BE_NODE && PROXY_TO_PTHREAD
211+
#if ENVIRONMENT_MAY_BE_NODE
212212
if (ENVIRONMENT_IS_NODE) {
213-
// Once the proxied main thread has finished, mark it as weakly
213+
// Once the worker is returned to the pool, mark it as weakly
214214
// referenced so that its existence does not prevent Node.js from
215-
// exiting. This has no effect if the worker is already weakly
215+
// exiting. This has no effect if the worker is already weakly
216216
// referenced.
217217
worker.unref();
218218
}
@@ -689,11 +689,17 @@ var LibraryPThread = {
689689
#endif
690690
#if ENVIRONMENT_MAY_BE_NODE
691691
if (ENVIRONMENT_IS_NODE) {
692+
#if HAS_MAIN
692693
// Mark worker as weakly referenced once we start executing a pthread,
693694
// so that its existence does not prevent Node.js from exiting. This
694695
// has no effect if the worker is already weakly referenced (e.g. if
695696
// this worker was previously idle/unused).
696697
worker.unref();
698+
#else
699+
// When there is no main() function treat the program as a library and mark the worker as
700+
// strongly referenced, so that it keeps Node.js alive while working.
701+
worker.ref();
702+
#endif
697703
}
698704
#endif
699705
// Ask the worker to start executing its pthread entry point function.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include <pthread.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <assert.h>
5+
#include <emscripten.h>
6+
#include <emscripten/proxying.h>
7+
#include <emscripten/threading.h>
8+
9+
void create_done(void* arg) {
10+
printf("done\n");
11+
// Notify JS to resolve promise
12+
EM_ASM({
13+
doneCallback();
14+
});
15+
}
16+
17+
void* worker(void* arg) {
18+
printf("worker starting\n");
19+
fflush(stdout);
20+
emscripten_thread_sleep(100);
21+
22+
// proxy back to the main thread
23+
emscripten_proxy_async(
24+
emscripten_proxy_get_system_queue(),
25+
emscripten_main_runtime_thread_id(),
26+
create_done,
27+
NULL
28+
);
29+
return NULL;
30+
}
31+
32+
EMSCRIPTEN_KEEPALIVE
33+
void create_thread_async() {
34+
pthread_t thread;
35+
int rc = pthread_create(&thread, NULL, worker, NULL);
36+
assert(rc == 0);
37+
pthread_detach(thread);
38+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
initialized
2+
worker starting
3+
done
4+
exiting
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
let resolveDone;
2+
const p = new Promise(resolve => resolveDone = resolve);
3+
4+
globalThis.doneCallback = function() {
5+
resolveDone();
6+
};
7+
8+
Module['onRuntimeInitialized'] = function() {
9+
console.log('initialized');
10+
Module['_create_thread_async']();
11+
p.then(() => {
12+
console.log('exiting');
13+
});
14+
};

test/test_core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9164,6 +9164,12 @@ def test_pthread_keepalive(self):
91649164
def test_pthread_weak_ref(self):
91659165
self.do_core_test('pthread/test_pthread_weak_ref.c')
91669166

9167+
@requires_pthreads
9168+
def test_pthread_exit_library(self):
9169+
# Test that Node.js doesn't exit while there are still pthreads running when there is no main function.
9170+
self.cflags += ['--post-js', test_file('core/pthread/test_pthread_exit_library.post.js')]
9171+
self.do_core_test('pthread/test_pthread_exit_library.c')
9172+
91679173
@requires_pthreads
91689174
def test_pthread_exit_main(self):
91699175
self.do_core_test('pthread/test_pthread_exit_main.c')

0 commit comments

Comments
 (0)