Skip to content

Commit 9757ac1

Browse files
authored
Make Node.js pthreads behavior in line with native (emscripten-core#19073)
By marking workers as weakly referenced. This corresponds to the ideal scenario as mentioned in commit a9cbf47.
1 parent f011b5c commit 9757ac1

14 files changed

+106
-15
lines changed

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ See docs/process.md for more on how version tagging works.
3030
completely removes the need for the `REVERSE_DEPS` settings which has now
3131
been deprecated. (#18905)
3232
- Bump the default minimum Firefox version from 65 to 68 (#19191).
33+
- Background pthreads no longer prevent a Node.js app from exiting. (#19073)
3334

3435
3.1.36 - 04/16/23
3536
-----------------

src/library_pthread.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,12 @@ var LibraryPThread = {
211211
// worker pool as an unused worker.
212212
worker.pthread_ptr = 0;
213213

214-
#if ENVIRONMENT_MAY_BE_NODE
214+
#if ENVIRONMENT_MAY_BE_NODE && PROXY_TO_PTHREAD
215215
if (ENVIRONMENT_IS_NODE) {
216-
// Once a pthread has finished and the worker becomes idle, mark it
217-
// as weakly referenced so that its existence does not prevent Node.js
218-
// from exiting.
216+
// Once the proxied main thread has finished, mark it as weakly
217+
// referenced so that its existence does not prevent Node.js from
218+
// exiting. This has no effect if the worker is already weakly
219+
// referenced.
219220
worker.unref();
220221
}
221222
#endif
@@ -286,7 +287,7 @@ var LibraryPThread = {
286287
cancelThread(d['thread']);
287288
} else if (cmd === 'loaded') {
288289
worker.loaded = true;
289-
#if ENVIRONMENT_MAY_BE_NODE
290+
#if ENVIRONMENT_MAY_BE_NODE && PTHREAD_POOL_SIZE
290291
// Check that this worker doesn't have an associated pthread.
291292
if (ENVIRONMENT_IS_NODE && !worker.pthread_ptr) {
292293
// Once worker is loaded & idle, mark it as weakly referenced,
@@ -571,6 +572,19 @@ var LibraryPThread = {
571572
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread });
572573
},
573574

575+
_emscripten_thread_set_strongref: function(thread) {
576+
// Called when a thread needs to be strongly referenced.
577+
// Currently only used for:
578+
// - keeping the "main" thread alive in PROXY_TO_PTHREAD mode;
579+
// - crashed threads that needs to propagate the uncaught exception
580+
// back to the main thread.
581+
#if ENVIRONMENT_MAY_BE_NODE
582+
if (ENVIRONMENT_IS_NODE) {
583+
PThread.pthreads[thread].ref();
584+
}
585+
#endif
586+
},
587+
574588
$cleanupThread: function(pthread_ptr) {
575589
#if PTHREADS_DEBUG
576590
dbg('cleanupThread: ' + ptrToString(pthread_ptr))
@@ -674,14 +688,16 @@ var LibraryPThread = {
674688
msg.moduleCanvasId = threadParams.moduleCanvasId;
675689
msg.offscreenCanvases = threadParams.offscreenCanvases;
676690
#endif
677-
// Ask the worker to start executing its pthread entry point function.
678691
#if ENVIRONMENT_MAY_BE_NODE
679692
if (ENVIRONMENT_IS_NODE) {
680-
// Mark worker as strongly referenced once we start executing a pthread,
681-
// so that Node.js doesn't exit while the pthread is running.
682-
worker.ref();
693+
// Mark worker as weakly referenced once we start executing a pthread,
694+
// so that its existence does not prevent Node.js from exiting. This
695+
// has no effect if the worker is already weakly referenced (e.g. if
696+
// this worker was previously idle/unused).
697+
worker.unref();
683698
}
684699
#endif
700+
// Ask the worker to start executing its pthread entry point function.
685701
worker.postMessage(msg, threadParams.transferList);
686702
return 0;
687703
},

src/library_sigs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ sigs = {
290290
_emscripten_set_offscreencanvas_size__sig: 'ipii',
291291
_emscripten_thread_exit_joinable__sig: 'vp',
292292
_emscripten_thread_mailbox_await__sig: 'vp',
293+
_emscripten_thread_set_strongref__sig: 'vp',
293294
_emscripten_throw_longjmp__sig: 'v',
294295
_gmtime_js__sig: 'vpp',
295296
_localtime_js__sig: 'vpp',

system/lib/libc/crt1_proxy_main.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <emscripten/threading.h>
1414
#include <emscripten/eventloop.h>
1515

16+
#include "threading_internal.h"
17+
1618
static int _main_argc;
1719
static char** _main_argv;
1820

@@ -49,5 +51,10 @@ EMSCRIPTEN_KEEPALIVE int _emscripten_proxy_main(int argc, char** argv) {
4951
pthread_t thread;
5052
int rc = pthread_create(&thread, &attr, _main_thread, NULL);
5153
pthread_attr_destroy(&attr);
54+
if (rc == 0) {
55+
// Mark the thread as strongly referenced, so that Node.js doesn't exit
56+
// while the pthread is running.
57+
_emscripten_thread_set_strongref(thread);
58+
}
5259
return rc;
5360
}

system/lib/pthread/emscripten_yield.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
#include "syscall.h"
88
#include "threading_internal.h"
99

10-
static _Atomic bool thread_crashed = false;
10+
static _Atomic pthread_t crashed_thread_id = NULL;
1111

1212
void _emscripten_thread_crashed() {
13-
thread_crashed = true;
13+
crashed_thread_id = pthread_self();
1414
}
1515

1616
static void dummy(double now)
@@ -28,7 +28,11 @@ void _emscripten_yield(double now) {
2828
// allocate (or otherwise itself crash) so use a low level atomic primitive
2929
// for this signal.
3030
if (is_runtime_thread) {
31-
if (thread_crashed) {
31+
if (crashed_thread_id) {
32+
// Mark the crashed thread as strongly referenced so that Node.js doesn't
33+
// exit while the pthread is propagating the uncaught exception back to
34+
// the main thread.
35+
_emscripten_thread_set_strongref(crashed_thread_id);
3236
// Return the event loop so we can handle the message from the crashed
3337
// thread.
3438
emscripten_exit_with_live_runtime();

system/lib/pthread/threading_internal.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ void __emscripten_thread_cleanup(pthread_t thread);
118118
hidden void* _emscripten_tls_init(void);
119119
hidden void _emscripten_tls_free(void);
120120

121+
// Marks the given thread as strongly referenced. This is used to prevent the
122+
// Node.js application from exiting as long as there are strongly referenced
123+
// threads still running. Normally you don't need to call this function, and
124+
// the pthread behaviour will match native in that background threads won't
125+
// keep runtime alive, but waiting for them via e.g. pthread_join will.
126+
// However, this is useful for features like PROXY_TO_PTHREAD where we want to
127+
// keep running as long as the detached pthread is.
128+
void _emscripten_thread_set_strongref(pthread_t thread);
129+
121130
// Checks certain structural invariants. This allows us to detect when
122131
// already-freed threads are used in some APIs. Technically this is undefined
123132
// behaviour, but we have a couple of places where we add these checks so that
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <assert.h>
2+
#include <pthread.h>
3+
#include <stdbool.h>
4+
#include <stdio.h>
5+
#include <unistd.h>
6+
7+
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
8+
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
9+
10+
bool running = false;
11+
12+
void *worker_thread(void *arg) {
13+
printf("worker_thread\n");
14+
15+
pthread_mutex_lock(&mutex);
16+
running = true;
17+
pthread_cond_signal(&cond);
18+
pthread_mutex_unlock(&mutex);
19+
20+
// Infinite loop
21+
while (1) {}
22+
23+
return NULL;
24+
}
25+
26+
int main() {
27+
pthread_t thread;
28+
29+
printf("main\n");
30+
int rc = pthread_create(&thread, NULL, worker_thread, NULL);
31+
assert(rc == 0);
32+
33+
pthread_mutex_lock(&mutex);
34+
35+
// Wait until the thread executes its entry point
36+
while (!running) {
37+
pthread_cond_wait(&cond, &mutex);
38+
}
39+
40+
pthread_mutex_unlock(&mutex);
41+
42+
printf("done\n");
43+
return 0;
44+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
main
2+
worker_thread
3+
done

test/other/metadce/test_metadce_minimal_pthreads.exports

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ C
44
D
55
E
66
F
7-
p
7+
G
88
q
99
r
1010
s

test/other/metadce/test_metadce_minimal_pthreads.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ a.l
1313
a.m
1414
a.n
1515
a.o
16+
a.p

0 commit comments

Comments
 (0)