We have a heavily multithreaded application that is built with JS exception support and after the Chromium update to 149 that was pushed recently, it started surfacing uncaught exceptions from pthread workers.
- Chromium 149 (Chrome, Edge) -> broken
- Chromium 148 -> works
- Firefox 151 -> works
We started getting:
Uncaught CppException:
at ___cxa_throw
Version of emscripten/emsdk:
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.19 (08e2de1031913e4ba7963b1c56f35f036a7d4d56)
clang version 22.0.0git (https:/github.com/llvm/llvm-project 12f392cff10fcc70b4ec4f01ab386922742e9136)
Target: wasm32-unknown-emscripten
Thread model: posix
Have also tested with latest 6.0.0
Full build command and output with -v appended for the MRE:
em++ -v -std=c++20 -pthread -sSTACK_SIZE=1MB -sINITIAL_MEMORY=8MB -sALLOW_MEMORY_GROWTH=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sPTHREADS_DEBUG -sDISABLE_EXCEPTION_CATCHING=0 main.cpp -o main.html
Verbose output of build command
"D:/emsdk/upstream/bin\clang++.exe" -target wasm32-unknown-emscripten -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-cxx-exceptions -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr '--sysroot=D:\emsdk\upstream\emscripten\cache\sysroot' -D__EMSCRIPTEN_SHARED_MEMORY__=1 -DEMSCRIPTEN -Xclang '-iwithsysroot/include\fakesdl' -Xclang '-iwithsysroot/include\compat' -v -std=c++20 -pthread -c main.cpp -o 'C:\Users\TmpUser~1\AppData\Local\Temp\1\emscripten_temp_o_9e2v1x\main.o'
clang version 22.0.0git (https:/github.com/llvm/llvm-project 12f392cff10fcc70b4ec4f01ab386922742e9136)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: D:\emsdk\upstream\bin
(in-process)
"D:\\emsdk\\upstream\\bin\\clang++.exe" -cc1 -triple wasm32-unknown-emscripten -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name main.cpp -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-feature +atomics -target-feature +bulk-memory -target-feature +mutable-globals -target-feature +sign-ext -target-cpu generic -fvisibility=hidden -debugger-tuning=gdb "-fdebug-compilation-dir=D:\\Sources\\emscripten-exception-mre\\minimal" -v "-fcoverage-compilation-dir=D:\\Sources\\emscripten-exception-mre\\minimal" -resource-dir "D:\\emsdk\\upstream\\lib\\clang\\22" -D __EMSCRIPTEN_SHARED_MEMORY__=1 -D EMSCRIPTEN -isysroot "D:\\emsdk\\upstream\\emscripten\\cache\\sysroot" -internal-isystem "D:\\emsdk\\upstream\\emscripten\\cache\\sysroot/include/wasm32-emscripten/c++/v1" -internal-isystem "D:\\emsdk\\upstream\\emscripten\\cache\\sysroot/include/c++/v1" -internal-isystem "D:\\emsdk\\upstream\\lib\\clang\\22\\include" -internal-isystem "D:\\emsdk\\upstream\\emscripten\\cache\\sysroot/include/wasm32-emscripten" -internal-isystem "D:\\emsdk\\upstream\\emscripten\\cache\\sysroot/include" -std=c++20 -fdeprecated-macro -ferror-limit 19 -fmessage-length=269 -pthread -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fcolor-diagnostics "-iwithsysroot/include\\fakesdl" "-iwithsysroot/include\\compat" -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-cxx-exceptions -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o "C:\\Users\\TmpUser~1\\AppData\\Local\\Temp\\1\\emscripten_temp_o_9e2v1x\\main.o" -x c++ main.cpp
clang -cc1 version 22.0.0git based upon LLVM 22.0.0git default target x86_64-pc-windows-msvc
ignoring nonexistent directory "D:\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "D:\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
D:\emsdk\upstream\emscripten\cache\sysroot/include\fakesdl
D:\emsdk\upstream\emscripten\cache\sysroot/include\compat
D:\emsdk\upstream\emscripten\cache\sysroot/include/c++/v1
D:\emsdk\upstream\lib\clang\22\include
D:\emsdk\upstream\emscripten\cache\sysroot/include
End of search list.
"D:/emsdk/upstream/bin\clang.exe" --version
em++: warning: -pthread + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271 [-Wpthreads-mem-growth]
"D:/emsdk/upstream/bin\wasm-ld.exe" -o main.wasm 'C:\Users\TmpUser~1\AppData\Local\Temp\1\emscripten_temp_o_9e2v1x\main.o' '-LD:\emsdk\upstream\emscripten\cache\sysroot\lib\wasm32-emscripten' '-LD:\emsdk\upstream\emscripten\src\lib' 'D:\emsdk\upstream\emscripten\cache\sysroot\lib\wasm32-emscripten\crtbegin.o' -lGL-mt-getprocaddr -lal -lhtml5 -lstubs-debug -lnoexit -lc-mt-debug -ldlmalloc-mt-debug -lcompiler_rt-mt -lc++-debug-mt -lc++abi-debug-mt -lsockets-mt -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-cxx-exceptions -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr 'C:\Users\TmpUser~1\AppData\Local\Temp\1\tmp53ofs8_9libemscripten_js_symbols.so' --import-memory --shared-memory --strip-debug --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_get_current --export=emscripten_stack_init --export=_emscripten_stack_alloc --export=_emscripten_thread_free_data --export=_emscripten_thread_crashed --export=__cxa_can_catch --export=__cxa_increment_exception_refcount --export=__cxa_decrement_exception_refcount --export=setThrew --export=__cxa_free_exception --export=__wasm_call_ctors --export=_emscripten_tls_init --export=_emscripten_thread_init --export=_emscripten_stack_restore --export=emscripten_stack_set_limits --export=_emscripten_thread_exit --export=__get_exception_message --export=free --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-if-defined=main --export-if-defined=__main_argc_argv --export-if-defined=fflush --export-table -z stack-size=1048576 --max-memory=2147483648 --initial-memory=8388608 --no-entry --stack-first --table-base=1
"D:/emsdk/upstream/bin\llvm-objcopy.exe" main.wasm main.wasm '--remove-section=.debug*' --remove-section=producers --remove-section=name
"D:/emsdk/node/22.16.0_64bit/bin/node.exe" 'D:\emsdk\upstream\emscripten\tools\compiler.mjs' -
"D:/emsdk/node/22.16.0_64bit/bin/node.exe" 'D:\emsdk\upstream\emscripten\tools\acorn-optimizer.mjs' 'C:\Users\TmpUser~1\AppData\Local\Temp\1\emscripten_temp_o_9e2v1x\main.js' growableHeap --closure-friendly -o 'C:\Users\TmpUser~1\AppData\Local\Temp\1\emscripten_temp_o_9e2v1x\main.jso1.js'
"D:/emsdk/node/22.16.0_64bit/bin/node.exe" 'D:\emsdk\upstream\emscripten\tools\preprocessor.mjs' - shell.html
MRE
The MRE uses INITIAL_MEMORY=8MB and STACK_SIZE=1MB so that spawning 8 threads forces a memory growth.
Running the MRE produces Uncaught RuntimeError: memory access out of bounds, while our main application surfaces Uncaught CppException. But I think these are likely symptoms of the same underlying issue.
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#include <emscripten.h>
void setupIOThreadPool(unsigned int numThreads) {
std::vector<std::thread> threads;
for (unsigned int i = 0; i < numThreads; ++i) {
threads.emplace_back([]() {
while (true) {
// Simulate some work
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
});
}
for (auto &t : threads) {
t.detach();
}
}
void testThrowCatchInThreads(unsigned int numThreads) {
std::vector<std::thread> threads;
threads.reserve(numThreads);
for (unsigned int i = 0; i < numThreads; ++i) {
threads.emplace_back([i]() {
try {
throw std::logic_error("Logic error Thread-" + std::to_string(i));
} catch (std::exception &e) {
std::cout << "Thread-" << i << " caught exception: " << e.what()
<< "\n";
}
});
}
for (auto &t : threads) {
t.detach();
}
}
int main() {
const auto numOfPoolThreads{4};
const auto numOfTestThreads{4};
setupIOThreadPool(numOfPoolThreads);
testThrowCatchInThreads(numOfTestThreads);
emscripten_exit_with_live_runtime();
}
Increasing INITIAL_MEMORY to avoid growth in the MRE works around the issue, but a memory growth can happen at any point during runtime, so in theory this can resurface again.
Switching from JS exceptions to Wasm exceptions -fwasm-exceptions does not seem to reproduce the issue.
Is this a defect of JS based exceptions or Chromium regression? Or are we doing something really wrong?
We have a heavily multithreaded application that is built with JS exception support and after the Chromium update to 149 that was pushed recently, it started surfacing uncaught exceptions from pthread workers.
We started getting:
Version of emscripten/emsdk:
Have also tested with latest
6.0.0Full build command and output with
-vappended for the MRE:em++ -v -std=c++20 -pthread -sSTACK_SIZE=1MB -sINITIAL_MEMORY=8MB -sALLOW_MEMORY_GROWTH=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sPTHREADS_DEBUG -sDISABLE_EXCEPTION_CATCHING=0 main.cpp -o main.htmlVerbose output of build command
MRE
The MRE uses
INITIAL_MEMORY=8MBandSTACK_SIZE=1MBso that spawning 8 threads forces a memory growth.Running the MRE produces
Uncaught RuntimeError: memory access out of bounds, while our main application surfacesUncaught CppException. But I think these are likely symptoms of the same underlying issue.Increasing
INITIAL_MEMORYto avoid growth in the MRE works around the issue, but a memory growth can happen at any point during runtime, so in theory this can resurface again.Switching from JS exceptions to Wasm exceptions
-fwasm-exceptionsdoes not seem to reproduce the issue.Is this a defect of JS based exceptions or Chromium regression? Or are we doing something really wrong?