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

Prevent idle Workers from keeping Node.js app alive #18227

Merged
merged 12 commits into from Nov 18, 2022
3 changes: 3 additions & 0 deletions src/closure-externs/node-externs.js
Expand Up @@ -111,3 +111,6 @@ Buffer.prototype.slice = function(start, end) {};
* @nosideeffects
*/
Buffer.prototype.toString = function(encoding, start, end) {};

Worker.prototype.ref = function() {};
Worker.prototype.unref = function() {};
29 changes: 26 additions & 3 deletions src/library_pthread.js
Expand Up @@ -205,6 +205,15 @@ var LibraryPThread = {
// worker pool as an unused worker.
worker.pthread_ptr = 0;

#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
// Once a pthread has finished and the worker becomes idle, mark it
// as weakly referenced so that its existence does not prevent Node.js
// from exiting.
worker.unref();
}
#endif

// Finally, free the underlying (and now-unused) pthread structure in
// linear memory.
__emscripten_thread_free_data(pthread_ptr);
Expand Down Expand Up @@ -267,11 +276,18 @@ var LibraryPThread = {
cancelThread(d['thread']);
} else if (cmd === 'loaded') {
worker.loaded = true;
#if ENVIRONMENT_MAY_BE_NODE
RReverser marked this conversation as resolved.
Show resolved Hide resolved
if (ENVIRONMENT_IS_NODE) {
// Once worker is loaded & idle, mark it as weakly referenced,
// so that mere existence of a Worker in the pool does not prevent
// Node.js from exiting the app.
worker.unref();
}
#endif
if (onFinishedLoading) onFinishedLoading(worker);
// If this Worker is already pending to start running a thread, launch the thread now
if (worker.runPthread) {
worker.runPthread();
delete worker.runPthread;
}
} else if (cmd === 'print') {
out('Thread ' + d['threadId'] + ': ' + d['text']);
Expand Down Expand Up @@ -584,12 +600,19 @@ var LibraryPThread = {
#endif
worker.runPthread = () => {
// Ask the worker to start executing its pthread entry point function.
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
// Mark worker as strongly referenced once we start executing a pthread,
// so that Node.js doesn't exit while the pthread is running.
worker.ref();
}
#endif
msg.time = performance.now();
worker.postMessage(msg, threadParams.transferList);
delete worker.runPthread;
};
if (worker.loaded) {
worker.runPthread();
delete worker.runPthread;
}
return 0;
},
Expand Down Expand Up @@ -671,7 +694,7 @@ var LibraryPThread = {

var offscreenCanvases = {}; // Dictionary of OffscreenCanvas objects we'll transfer to the created thread to own
var moduleCanvasId = Module['canvas'] ? Module['canvas'].id : '';
// Note that transferredCanvasNames might be null (so we cannot do a for-of loop).
// Note that transferredCanvasNames might be null (so we cannot do a for-of loop).
for (var i in transferredCanvasNames) {
var name = transferredCanvasNames[i].trim();
var offscreenCanvasInfo;
Expand Down
18 changes: 0 additions & 18 deletions test/test_core.py
Expand Up @@ -2766,7 +2766,6 @@ def test_pthread_proxying_cpp(self):

@node_pthreads
def test_pthread_proxying_dropped_work(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE=2')
self.do_run_in_out_file_test('pthread/test_pthread_proxying_dropped_work.c')

Expand All @@ -2779,7 +2778,6 @@ def test_pthread_proxying_refcount(self):

@node_pthreads
def test_pthread_dispatch_after_exit(self):
self.set_setting('EXIT_RUNTIME')
self.do_run_in_out_file_test('pthread/test_pthread_dispatch_after_exit.c', interleaved_output=False)

@node_pthreads
Expand All @@ -2791,7 +2789,6 @@ def test_pthread_atexit(self):

@node_pthreads
def test_pthread_nested_work_queue(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 1)
self.do_run_in_out_file_test('pthread/test_pthread_nested_work_queue.c')

Expand All @@ -2804,13 +2801,11 @@ def test_pthread_thread_local_storage(self):

@node_pthreads
def test_pthread_cleanup(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 4)
self.do_run_in_out_file_test('pthread/test_pthread_cleanup.cpp')

@node_pthreads
def test_pthread_setspecific_mainthread(self):
self.set_setting('EXIT_RUNTIME')
print('.. return')
self.do_runf(test_file('pthread/test_pthread_setspecific_mainthread.c'), 'done!', emcc_args=['-DRETURN'])
print('.. exit')
Expand All @@ -2820,7 +2815,6 @@ def test_pthread_setspecific_mainthread(self):

@node_pthreads
def test_pthread_attr_getstack(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 1)
self.do_run_in_out_file_test('pthread/test_pthread_attr_getstack.c')

Expand All @@ -2836,7 +2830,6 @@ def test_pthread_abort(self):

@node_pthreads
def test_pthread_abort_interrupt(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 1)
expected = ['Aborted(). Build with -sASSERTIONS for more info', 'Aborted(native code called abort())']
self.do_runf(test_file('pthread/test_pthread_abort_interrupt.c'), expected, assert_returncode=NON_ZERO)
Expand Down Expand Up @@ -9112,13 +9105,11 @@ def test_pthread_c11_threads(self):
@node_pthreads
def test_pthread_cxx_threads(self):
self.set_setting('PTHREAD_POOL_SIZE', 1)
self.set_setting('EXIT_RUNTIME')
self.do_run_in_out_file_test('pthread/test_pthread_cxx_threads.cpp')

@node_pthreads
def test_pthread_busy_wait(self):
self.set_setting('PTHREAD_POOL_SIZE', 1)
self.set_setting('EXIT_RUNTIME')
self.do_run_in_out_file_test('pthread/test_pthread_busy_wait.cpp')

@node_pthreads
Expand Down Expand Up @@ -9167,11 +9158,9 @@ def test_pthread_exit_process(self):

@node_pthreads
def test_pthread_exit_main(self):
self.set_setting('EXIT_RUNTIME')
self.do_run_in_out_file_test('core/pthread/test_pthread_exit_main.c')

def test_pthread_exit_main_stub(self):
self.set_setting('EXIT_RUNTIME')
self.do_run_in_out_file_test('core/pthread/test_pthread_exit_main.c')

@node_pthreads
Expand Down Expand Up @@ -9223,7 +9212,6 @@ def test_emscripten_futexes(self):
@node_pthreads
def test_stdio_locking(self):
self.set_setting('PTHREAD_POOL_SIZE', '2')
self.set_setting('EXIT_RUNTIME')
self.do_run_in_out_file_test('core/test_stdio_locking.c')

@needs_dylink
Expand All @@ -9238,7 +9226,6 @@ def test_pthread_dylink_basics(self):
@node_pthreads
def test_pthread_dylink(self):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.set_setting('PTHREAD_POOL_SIZE', 2)
main = test_file('core/pthread/test_pthread_dylink.c')
Expand All @@ -9259,7 +9246,6 @@ def test_pthread_dylink(self):
@node_pthreads
def test_pthread_dylink_entry_point(self, args):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.set_setting('PTHREAD_POOL_SIZE', 1)
main = test_file('core/pthread/test_pthread_dylink_entry_point.c')
Expand All @@ -9269,7 +9255,6 @@ def test_pthread_dylink_entry_point(self, args):
@node_pthreads
def test_pthread_dylink_exceptions(self):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.emcc_args.append('-fexceptions')
self.dylink_testf(test_file('core/pthread/test_pthread_dylink_exceptions.cpp'))
Expand Down Expand Up @@ -9312,7 +9297,6 @@ def test_pthread_dlsym(self):
@node_pthreads
def test_pthread_dylink_tls(self):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.set_setting('PTHREAD_POOL_SIZE', 1)
main = test_file('core/pthread/test_pthread_dylink_tls.c')
Expand All @@ -9322,7 +9306,6 @@ def test_pthread_dylink_tls(self):
@node_pthreads
def test_pthread_dylink_longjmp(self):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.set_setting('PTHREAD_POOL_SIZE=1')
main = test_file('core/pthread/test_pthread_dylink_longjmp.c')
Expand All @@ -9332,7 +9315,6 @@ def test_pthread_dylink_longjmp(self):
@node_pthreads
def test_pthread_dylink_main_module_1(self):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.set_setting('MAIN_MODULE')
self.do_runf(test_file('hello_world.c'))
Expand Down
1 change: 0 additions & 1 deletion test/test_other.py
Expand Up @@ -9101,7 +9101,6 @@ def test_node_js_pthread_module(self):
const test_module = require("./module");
test_module().then((test_module_instance) => {
test_module_instance._main();
process.exit(0);
});
'''
ensure_dir('subdir')
Expand Down