Skip to content

Commit 4e81f0a

Browse files
bghgaryCopilot
andauthored
Add ThreadSanitizer CI jobs (Linux + macOS) (#148)
Add ThreadSanitizer (TSan) support for detecting data races at runtime. [Created by Copilot on behalf of @bghgary] ## Changes - **CMakeLists.txt**: New `ENABLE_THREAD_SANITIZER` option with `-fsanitize=thread`. Includes a mutual exclusion check with `ENABLE_SANITIZERS` since TSan and ASan cannot be combined. - **.github/workflows/build-linux.yml**: New `enable-thread-sanitizer` input, passed as `ENABLE_THREAD_SANITIZER` to CMake. Wires `TSAN_OPTIONS` with suppression file and sets `JSC_useConcurrentGC=0` for the Run Tests step (see below). - **.github/workflows/build-macos.yml**: New `enable-thread-sanitizer` input; wires `TSAN_OPTIONS`. - **.github/workflows/ci.yml**: New `Ubuntu_ThreadSanitizer_clang` and `macOS_Xcode164_ThreadSanitizer` jobs. - **.github/tsan_suppressions.txt**: Suppresses JSC-internal data races on Ubuntu (`called_from_lib:libjavascriptcoregtk`). These are allocator-level races in JSC's JIT worker threads that we cannot fix. ## Linux TSan: Concurrent GC Workaround JSC on Linux uses `pthread_kill(tid, SIGUSR1)` + `sem_wait` in `WTF::Thread::suspend()` to stop mutator threads at GC safepoints. TSan's signal interception defers SIGUSR1 delivery indefinitely when the target is inside instrumented code; the handler never runs and the Collector Thread's `sem_wait` deadlocks. macOS JSC uses Mach `thread_suspend()` (no Unix signals) and is unaffected. Setting `JSC_useConcurrentGC=0` on the Linux TSan job removes the dedicated Collector Thread; GC runs on the mutator without cross-thread signals. Reproduced locally in WSL Ubuntu: default configuration hung 9/20 runs; with `JSC_useConcurrentGC=0`, 0/30 runs hung. ## Dependency Updates - **arcana.cpp**: Points to upstream `microsoft/arcana.cpp` (includes #61 — TSan-safe test hook callback mutex). - **UrlLib**: Points to upstream `BabylonJS/UrlLib` at `d251ad44` (includes BabylonJS/UrlLib#27 — Apple WebSocket `@synchronized` fixes). ## Platform Support TSan is supported on Linux and macOS with Clang/GCC. MSVC does not support TSan and Clang targeting Windows does not have a TSan runtime library. ## CI Impact The TSan jobs run in parallel with other jobs and do not increase overall pipeline time. ## Status - macOS TSan: passes clean - Ubuntu TSan: JSC-internal races suppressed via `called_from_lib:libjavascriptcoregtk`; concurrent GC disabled to avoid TSan/signal deadlock --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 49e2980 commit 4e81f0a

5 files changed

Lines changed: 72 additions & 3 deletions

File tree

.github/tsan_suppressions.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# ThreadSanitizer suppressions for JsRuntimeHost
2+
#
3+
# These suppress data races internal to third-party libraries that we cannot fix.
4+
# TSan on macOS (JavaScriptCore via Xcode) passes clean — these suppressions are
5+
# only needed for the Ubuntu JSC build (libjavascriptcoregtk).
6+
7+
# JavaScriptCore internal races in libjavascriptcoregtk.
8+
# Races manifest in TSan interceptors (free/malloc/memcpy/close) called from
9+
# JSC's JIT worker threads. function-name suppressions are needed because the
10+
# top frame is a libc interceptor attributed to UnitTests, not libjavascriptcoregtk.
11+
called_from_lib:libjavascriptcoregtk
12+
race:free
13+
race:close
14+
race:memcpy
15+
16+
# JSC signal handler that doesn't save/restore errno
17+
signal:libjavascriptcoregtk

.github/workflows/build-linux.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ on:
1515
required: false
1616
type: boolean
1717
default: false
18+
enable-thread-sanitizer:
19+
required: false
20+
type: boolean
21+
default: false
1822

1923
jobs:
2024
build:
@@ -36,6 +40,7 @@ jobs:
3640
cmake -B Build/ubuntu -G Ninja \
3741
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
3842
-D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} \
43+
-D ENABLE_THREAD_SANITIZER=${{ inputs.enable-thread-sanitizer && 'ON' || 'OFF' }} \
3944
-D CMAKE_C_COMPILER=${{ inputs.cc }} \
4045
-D CMAKE_CXX_COMPILER=${{ inputs.cxx }}
4146
@@ -45,3 +50,13 @@ jobs:
4550
- name: Run Tests
4651
working-directory: Build/ubuntu/Tests/UnitTests
4752
run: ./UnitTests
53+
env:
54+
TSAN_OPTIONS: ${{ inputs.enable-thread-sanitizer && format('suppressions={0}/.github/tsan_suppressions.txt', github.workspace) || '' }}
55+
# JSC's concurrent GC on Linux uses SIGUSR1 + sem_wait to suspend mutator
56+
# threads at safepoints. TSan's signal interception delays SIGUSR1 delivery
57+
# indefinitely, deadlocking the Collector Thread's sem_wait. Disabling the
58+
# concurrent collector removes the dedicated Collector Thread, so GC runs
59+
# on the mutator without cross-thread signals. macOS JSC uses Mach
60+
# thread_suspend() and is unaffected.
61+
JSC_useConcurrentGC: ${{ inputs.enable-thread-sanitizer && '0' || '' }}
62+

.github/workflows/build-macos.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ on:
1414
required: false
1515
type: boolean
1616
default: false
17+
enable-thread-sanitizer:
18+
required: false
19+
type: boolean
20+
default: false
1721

1822
jobs:
1923
build:
@@ -28,11 +32,15 @@ jobs:
2832
- name: Configure CMake
2933
run: |
3034
cmake -B Build/macOS -G Xcode \
31-
-D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }}
35+
-D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} \
36+
-D ENABLE_THREAD_SANITIZER=${{ inputs.enable-thread-sanitizer && 'ON' || 'OFF' }}
3237
3338
- name: Build
3439
run: cmake --build Build/macOS --target UnitTests --config RelWithDebInfo
3540

3641
- name: Run Tests
3742
working-directory: Build/macOS/Tests/UnitTests/RelWithDebInfo
3843
run: ./UnitTests
44+
env:
45+
TSAN_OPTIONS: ${{ inputs.enable-thread-sanitizer && format('suppressions={0}/.github/tsan_suppressions.txt', github.workspace) || '' }}
46+

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ jobs:
8383
runs-on: macos-latest
8484
enable-sanitizers: true
8585

86+
macOS_Xcode164_ThreadSanitizer:
87+
uses: ./.github/workflows/build-macos.yml
88+
with:
89+
xcode-version: '16.4'
90+
runs-on: macos-latest
91+
enable-thread-sanitizer: true
92+
93+
8694
# ── iOS ───────────────────────────────────────────────────────
8795
iOS_Xcode164:
8896
uses: ./.github/workflows/build-ios.yml
@@ -114,3 +122,10 @@ jobs:
114122
cc: clang
115123
cxx: clang++
116124
enable-sanitizers: true
125+
126+
Ubuntu_ThreadSanitizer_clang:
127+
uses: ./.github/workflows/build-linux.yml
128+
with:
129+
cc: clang
130+
cxx: clang++
131+
enable-thread-sanitizer: true

CMakeLists.txt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ FetchContent_Declare(AndroidExtensions
1515
EXCLUDE_FROM_ALL)
1616
FetchContent_Declare(arcana.cpp
1717
GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git
18-
GIT_TAG b9bf9d85fce37d5fc9dbfc4a4dc5e1531bee215a
18+
GIT_TAG 0b9f9b6d761909fbca9d3ab1f2d8ff1c3d25ed3d
1919
EXCLUDE_FROM_ALL)
2020
FetchContent_Declare(asio
2121
GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git
@@ -37,7 +37,7 @@ FetchContent_Declare(llhttp
3737
EXCLUDE_FROM_ALL)
3838
FetchContent_Declare(UrlLib
3939
GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git
40-
GIT_TAG 880c2575e57ca0b59068ecc4860f185b9970e0ce
40+
GIT_TAG d251ad44015e1ee6cd071514cb863d8ffc220f16
4141
EXCLUDE_FROM_ALL)
4242
# --------------------------------------------------
4343

@@ -85,6 +85,11 @@ option(JSRUNTIMEHOST_POLYFILL_TEXTDECODER "Include JsRuntimeHost Polyfill TextDe
8585

8686
# Sanitizers
8787
option(ENABLE_SANITIZERS "Enable AddressSanitizer and UBSan" OFF)
88+
option(ENABLE_THREAD_SANITIZER "Enable ThreadSanitizer" OFF)
89+
90+
if(ENABLE_SANITIZERS AND ENABLE_THREAD_SANITIZER)
91+
message(FATAL_ERROR "ENABLE_SANITIZERS and ENABLE_THREAD_SANITIZER cannot be used together.")
92+
endif()
8893

8994
if(ENABLE_SANITIZERS)
9095
set(ENABLE_RTTI ON CACHE BOOL "" FORCE)
@@ -111,6 +116,15 @@ if(ENABLE_SANITIZERS)
111116
endif()
112117
endif()
113118

119+
if(ENABLE_THREAD_SANITIZER)
120+
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
121+
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer)
122+
add_link_options(-fsanitize=thread)
123+
else()
124+
message(WARNING "ThreadSanitizer not supported on this compiler.")
125+
endif()
126+
endif()
127+
114128
# --------------------------------------------------
115129

116130
if(JSRUNTIMEHOST_TESTS)

0 commit comments

Comments
 (0)