NodeJS fails on `uv_thread_join` with thread sanitizers

Open
#848 4 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@SankeerthNara is already working on this.

Since Aug 13, 2026.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Bug
Clarity
Needs clarification
Activity status
Quiet
Tech stack
c, cmake, cpp, nodejs

Research direction

Start by running metacall-node-python-callback-testd with the reported TSAN_OPTIONS and compare the failure with the trace from loader/source/loader_impl.c:811 and metacall_node_python_callback_test.cpp:51. Review the sanitizer configuration and callback test setup; done means identifying and resolving the uv_thread_join/TLS or reported Node.js race without introducing sanitizer failures.

Written by the indexing model from the issue text.

Description

bug

🐛 Bug Report

Apparently it's happening during the destruction of the TLS.
I could get more by adding the following flags to TSAN:

	set(TESTS_SANITIZER_ENVIRONMENT_VARIABLES
		"TSAN_OPTIONS=verbosity=2:halt_on_error=1:history_size=7:report_signal_unsafe=1:report_thread_leaks=1:second_deadlock_stack=1:symbolize=1:suppressions=${CMAKE_SOURCE_DIR}/source/tests/sanitizer/tsan.supp"
	)

Then I got this:

==24215==__tls_get_addr: DTLS_Find 0x7f5445772690 6 
==24215==__tls_get_addr: DTLS_Find 0x7f5445772690 6 
==24215==__tls_get_addr: DTLS_Find 0x7f5445772690 6 
==24215==__tls_get_addr: DTLS_Find 0x7f5445772690 6 
==24215==__tls_get_addr: DTLS_Find 0x7f5445772690 6 
==24215==__tls_get_addr: DTLS_Find 0x7f5445772690 6 
==24215==__tls_get_addr: DTLS_Destroy 0x7f5445772690 
==24215==__tls_get_addr: DTLS_Deallocate 0x7f544ee00000 
ThreadSanitizer: CHECK failed: tsan_interceptors_posix.cpp:2058 "((thr->slot)) != (0)" (0x0, 0x0) (tid=24217)

Then running with strace got me the real error (apparently):

export PY_PORT_LIBRARY_PATH=/usr/local/metacall/source/ports/py_port
strace -f -e trace=exit,exit_group,rt_sigaction,rt_sigprocmask ./metacall-node-python-callback-testd

This is the real error:

WARNING: ThreadSanitizer: data race (pid=24227)
  Write of size 1 at 0x723000027cf8 by thread T1 (mutexes: write M0, write M1, write M2):
    #0 pthread_mutex_destroy ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1332 (libtsan.so.2+0x52bcc) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 v8::platform::DefaultJobState::~DefaultJobState() <null> (libnode.so.115+0xfd822c) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19)

  Previous atomic read of size 1 at 0x723000027cf8 by thread T6:
    #0 pthread_mutex_lock ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1341 (libtsan.so.2+0x558ce) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 v8::platform::DefaultJobState::CanRunFirstTask() <null> (libnode.so.115+0xfd855b) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19)

  Location is heap block of size 192 at 0x723000027cc0 allocated by thread T1:
    #0 operator new(unsigned long) ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:64 (libtsan.so.2+0x9c506) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 v8::platform::NewDefaultJobHandle(v8::Platform*, v8::TaskPriority, std::unique_ptr<v8::JobTask, std::default_delete<v8::JobTask> >, unsigned long) <null> (libnode.so.115+0xfd9f2a) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19)

  Mutex M0 (0x72140000a0a0) created at:
    #0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1315 (libtsan.so.2+0x5545f) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 v8::internal::ThreadManager::ThreadManager(v8::internal::Isolate*) <null> (libnode.so.115+0x1189221) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19)

  Mutex M1 (0x7238000070a0) created at:
    #0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1315 (libtsan.so.2+0x5545f) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 v8::base::RecursiveMutex::RecursiveMutex() <null> (libnode.so.115+0x19c7161) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19)

  Mutex M2 (0x72c00001df30) created at:
    #0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1315 (libtsan.so.2+0x5545f) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 v8::internal::Heap::Heap() <null> (libnode.so.115+0x12017cf) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19)

  Thread T1 (tid=24229, running) created by main thread at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1022 (libtsan.so.2+0x568a6) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 uv_thread_create_ex <null> (libuv.so.1+0x1ead3) (BuildId: 9ee7ac673eb757eba258b40525cf4de9bbd2de16)
    #2 loader_impl_initialize /usr/local/metacall/source/loader/source/loader_impl.c:811 (libmetacalld.so+0x35ed6) (BuildId: 0f692433c24e86392d1358abd90d2cbf294670ca)
    #3 loader_impl_load_from_file /usr/local/metacall/source/loader/source/loader_impl.c:1383 (libmetacalld.so+0x363f9) (BuildId: 0f692433c24e86392d1358abd90d2cbf294670ca)
    #4 loader_load_from_file /usr/local/metacall/source/loader/source/loader.c:353 (libmetacalld.so+0x32932) (BuildId: 0f692433c24e86392d1358abd90d2cbf294670ca)
    #5 metacall_load_from_file_ex /usr/local/metacall/source/metacall/source/metacall.c:505 (libmetacalld.so+0x388dd) (BuildId: 0f692433c24e86392d1358abd90d2cbf294670ca)
    #6 metacall_load_from_file /usr/local/metacall/source/metacall/source/metacall.c:480 (libmetacalld.so+0x3892d) (BuildId: 0f692433c24e86392d1358abd90d2cbf294670ca)
    #7 metacall_node_callback_test_DefaultConstructor_Test::TestBody() /usr/local/metacall/source/tests/metacall_node_python_callback_test/source/metacall_node_python_callback_test.cpp:51 (metacall-node-python-callback-testd+0x37ed) (BuildId: e8e807ce30b780a73daa6973923972f26145916e)
    #8 void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:2653 (libgtest.so.1.16.0+0x72f7d) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #9 void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:2689 (libgtest.so.1.16.0+0x72f7d)
    #10 testing::Test::Run() /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:2728 (libgtest.so.1.16.0+0x4d30b) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #11 testing::TestInfo::Run() /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:2874 (libgtest.so.1.16.0+0x4d755) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #12 testing::TestSuite::Run() /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:3052 (libgtest.so.1.16.0+0x53bc6) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #13 testing::internal::UnitTestImpl::RunAllTests() /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:6004 (libgtest.so.1.16.0+0x6239d) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #14 bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:2653 (libgtest.so.1.16.0+0x73529) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #15 bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:2689 (libgtest.so.1.16.0+0x73529)
    #16 testing::UnitTest::Run() /usr/local/metacall/build/_deps/googletest-src/googletest/src/gtest.cc:5583 (libgtest.so.1.16.0+0x4d43e) (BuildId: 6877dc67c273608d78762837c6012c5df4e00c62)
    #17 RUN_ALL_TESTS() /usr/local/metacall/build/_deps/googletest-src/googletest/include/gtest/gtest.h:2334 (metacall-node-python-callback-testd+0x3549) (BuildId: e8e807ce30b780a73daa6973923972f26145916e)
    #18 main /usr/local/metacall/source/tests/metacall_node_python_callback_test/source/main.cpp:27 (metacall-node-python-callback-testd+0x3549)

  Thread T6 (tid=24234, running) created by thread T1 at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1022 (libtsan.so.2+0x568a6) (BuildId: 99ef88596cb10a8ccf307fe1a9070f66a44b1624)
    #1 uv_thread_create_ex <null> (libuv.so.1+0x1ead3) (BuildId: 9ee7ac673eb757eba258b40525cf4de9bbd2de16)

SUMMARY: ThreadSanitizer: data race (/usr/lib/x86_64-linux-gnu/libnode.so.115+0xfd822c) (BuildId: 9cae2928171fe8cd59431ad6df3e33dc66743b19) in v8::platform::DefaultJobState::~DefaultJobState()

Here is the full signal trace:

[pid 24229] rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], [], 8) = 0
[pid 24229] rt_sigaction(SIGWINCH, {sa_handler=SIG_DFL, sa_mask=~[], sa_flags=SA_RESTORER, sa_restorer=0x7f7886ee2df0}, NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_BLOCK, [TTOU], NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_UNBLOCK, [TTOU], NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_BLOCK, [TTOU], NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_UNBLOCK, [TTOU], NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_BLOCK, [TTOU], NULL, 8) = 0
[pid 24229] rt_sigprocmask(SIG_UNBLOCK, [TTOU], NULL, 8) = 0
[pid 24229] rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGINT, NULL, {sa_handler=0x7f7887462be0, sa_mask=~[KILL STOP], sa_flags=SA_RESTORER|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGQUIT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGILL, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGTRAP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
[pid 24229] rt_sigaction(SIGABRT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 24229] rt_sigaction(SIGBUS, NULL, {sa_handler=0x7f78874b30d0, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_NODEFER|SA_SIGINFO, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGFPE, NULL, {sa_handler=0x7f78874b30d0, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_NODEFER|SA_SIGINFO, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGUSR1, NULL, {sa_handler=0x7f7887462be0, sa_mask=~[KILL STOP], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGSEGV, NULL, {sa_handler=0x7f7887462be0, sa_mask=~[KILL STOP], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGUSR2, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=~[], sa_flags=SA_RESTORER, sa_restorer=0x7f7886ee2df0}, NULL, 8) = 0
[pid 24229] rt_sigaction(SIGALRM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGTERM, NULL, {sa_handler=0x7f7887462be0, sa_mask=~[KILL STOP], sa_flags=SA_RESTORER|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGSTKFLT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGCONT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGTSTP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGTTIN, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGTTOU, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGURG, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGXCPU, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGXFSZ, {sa_handler=SIG_IGN, sa_mask=~[], sa_flags=SA_RESTORER, sa_restorer=0x7f7886ee2df0}, NULL, 8) = 0
[pid 24229] rt_sigaction(SIGVTALRM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGPROF, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGWINCH, NULL, {sa_handler=SIG_DFL, sa_mask=~[KILL STOP], sa_flags=SA_RESTORER, sa_restorer=0x7f7886ee2df0}, 8) = 0
[pid 24229] rt_sigaction(SIGIO, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGPWR, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24229] rt_sigaction(SIGSYS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 24234] rt_sigprocmask(SIG_BLOCK, ~[RT_1], NULL, 8) = 0
[pid 24234] exit(0)                     = ?
[pid 24234] +++ exited with 0 +++
[pid 24231] rt_sigprocmask(SIG_BLOCK, ~[RT_1],  <unfinished ...>
[pid 24230] rt_sigprocmask(SIG_BLOCK, ~[RT_1],  <unfinished ...>
[pid 24231] <... rt_sigprocmask resumed>NULL, 8) = 0
[pid 24230] <... rt_sigprocmask resumed>NULL, 8) = 0
[pid 24230] exit(0 <unfinished ...>
[pid 24231] exit(0 <unfinished ...>
[pid 24230] <... exit resumed>)         = ?
[pid 24230] +++ exited with 0 +++
[pid 24231] <... exit resumed>)         = ?
[pid 24231] +++ exited with 0 +++
[pid 24233] rt_sigprocmask(SIG_BLOCK, ~[RT_1], NULL, 8) = 0
[pid 24233] exit(0)                     = ?
[pid 24233] +++ exited with 0 +++
[pid 24232] rt_sigprocmask(SIG_BLOCK, ~[RT_1], NULL, 8) = 0
[pid 24232] exit(0)                     = ?
[pid 24232] +++ exited with 0 +++



THREAD EXIT after node::Start



[pid 24229] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xd0} ---
ThreadSanitizer: CHECK failed: tsan_interceptors_posix.cpp:2058 "((thr->slot)) != (0)" (0x0, 0x0) (tid=24229)
[pid 24229] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x14f88} ---
[pid 24235] +++ killed by SIGSEGV (core dumped) +++
[pid 24228] +++ killed by SIGSEGV (core dumped) +++
[pid 24229] +++ killed by SIGSEGV (core dumped) +++
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

The issue happens here:
https://github.com/metacall/core/blob/67687c603b343d4fb5fb73b28601a594569a1e6d/source/loaders/node_loader/source/node_loader_impl.cpp#L4906

After it exits from the thread.

The following tests are involved, all of them share the same nature, they launch node from python. I have disabled the python async support and the issue still persist so it's unrelated to that:

#15 789.1 	 49 - metacall-node-python-port-mock-test (SEGFAULT)    metacall-node-python-port-mock-test
#15 789.1 	 50 - metacall-node-python-port-ruby-test (SEGFAULT)    metacall-node-python-port-ruby-test
#15 789.1 	 52 - metacall-node-callback-test (SEGFAULT)            metacall-node-callback-test
#15 789.1 	 57 - metacall-node-python-async-after-destroy-test (SEGFAULT) metacall-node-python-async-after-destroy-test
#15 789.1 	 58 - metacall-node-python-await-test (SEGFAULT)        metacall-node-python-await-test
#15 789.1 	 59 - metacall-node-python-exception-test (SEGFAULT)    metacall-node-python-exception-test

All of them show the same assert error:

ThreadSanitizer: CHECK failed: tsan_interceptors_posix.cpp:2058 "((thr->slot)) != (0)" (0x0, 0x0) (tid=9917)

I am not sure if this an issue of nodejs itself or it is related to metacall node_loader and how the destruction mechanism is handled.

Dominant language
C
Stars
1.8k
Forks
266
Avg merge
2d 16h
Merged PRs (30d)
14

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from metacall/core

All issues in metacall/core

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.