Skip to content

Commit a1e0fb1

Browse files
committed
ci: add TSan job with instrumented libc++
Add a ThreadSanitizer configuration to the Bitcoin Core CI workflow, using instrumented libc++ (matching Bitcoin Core's own TSan CI setup). Uses Bitcoin Core's depends build system to compile all dependencies (capnproto, boost, libevent, sqlite) with the instrumented libc++ for correct ABI compatibility. Both the instrumented libc++ build and the depends output are cached via GitHub Actions cache for faster subsequent runs.
1 parent 3115b09 commit a1e0fb1

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

.github/workflows/bitcoin-core-ci.yml

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ concurrency:
1919
env:
2020
BITCOIN_REPO: bitcoin/bitcoin
2121
LLVM_VERSION: 22
22+
LIBCXX_DIR: /tmp/libcxx-build/
2223

2324
jobs:
2425
bitcoin-core:
@@ -160,3 +161,199 @@ jobs:
160161
with:
161162
path: ${{ env.CCACHE_DIR }}
162163
key: ccache-${{ matrix.name }}-${{ github.ref }}-${{ github.sha }}
164+
165+
bitcoin-core-tsan:
166+
name: TSan
167+
runs-on: ubuntu-24.04
168+
timeout-minutes: 180
169+
170+
env:
171+
CCACHE_MAXSIZE: 400M
172+
CCACHE_DIR: ${{ github.workspace }}/.ccache
173+
LIBCXX_FLAGS: >-
174+
-fsanitize=thread
175+
-nostdinc++
176+
-nostdlib++
177+
-isystem /tmp/libcxx-build/include/c++/v1
178+
-L/tmp/libcxx-build/lib
179+
-Wl,-rpath,/tmp/libcxx-build/lib
180+
-lc++
181+
-lc++abi
182+
-lpthread
183+
-Wno-unused-command-line-argument
184+
TSAN_OPTIONS: suppressions=${{ github.workspace }}/test/sanitizer_suppressions/tsan:halt_on_error=1:second_deadlock_stack=1
185+
186+
steps:
187+
- name: Checkout Bitcoin Core
188+
uses: actions/checkout@v4
189+
with:
190+
repository: ${{ env.BITCOIN_REPO }}
191+
fetch-depth: 1
192+
193+
- name: Add LLVM apt repository
194+
run: |
195+
curl -s "https://apt.llvm.org/llvm-snapshot.gpg.key" | sudo tee "/etc/apt/trusted.gpg.d/apt.llvm.org.asc" > /dev/null
196+
source /etc/os-release
197+
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-${LLVM_VERSION} main" | sudo tee "/etc/apt/sources.list.d/llvm.list"
198+
sudo apt-get update
199+
200+
- name: Install packages
201+
run: |
202+
sudo apt-get install --no-install-recommends -y \
203+
"clang-${LLVM_VERSION}" \
204+
"llvm-${LLVM_VERSION}" \
205+
"llvm-${LLVM_VERSION}-dev" \
206+
"libclang-${LLVM_VERSION}-dev" \
207+
"libclang-rt-${LLVM_VERSION}-dev" \
208+
ninja-build \
209+
pkgconf \
210+
python3-pip \
211+
bison
212+
sudo update-alternatives --install /usr/bin/clang++ clang++ "/usr/bin/clang++-${LLVM_VERSION}" 100
213+
sudo update-alternatives --install /usr/bin/clang clang "/usr/bin/clang-${LLVM_VERSION}" 100
214+
sudo update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer "/usr/bin/llvm-symbolizer-${LLVM_VERSION}" 100
215+
sudo update-alternatives --set clang "/usr/bin/clang-${LLVM_VERSION}"
216+
sudo update-alternatives --set clang++ "/usr/bin/clang++-${LLVM_VERSION}"
217+
sudo update-alternatives --set llvm-symbolizer "/usr/bin/llvm-symbolizer-${LLVM_VERSION}"
218+
pip3 install --break-system-packages pycapnp
219+
220+
- name: Restore instrumented libc++ cache
221+
id: libcxx-cache
222+
uses: actions/cache@v4
223+
with:
224+
path: ${{ env.LIBCXX_DIR }}
225+
key: libcxx-Thread-llvmorg-${{ env.LLVM_VERSION }}.1.0
226+
227+
- name: Build instrumented libc++
228+
if: steps.libcxx-cache.outputs.cache-hit != 'true'
229+
run: |
230+
export PATH="/usr/lib/llvm-${LLVM_VERSION}/bin:$PATH"
231+
ls -l /usr/bin/clang /usr/bin/clang++ /usr/bin/llvm-symbolizer
232+
which clang clang++ llvm-symbolizer
233+
clang --version
234+
clang++ --version
235+
"/usr/bin/clang-${LLVM_VERSION}" --version
236+
"/usr/bin/clang++-${LLVM_VERSION}" --version
237+
git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-${LLVM_VERSION}.1.0" /tmp/llvm-project
238+
cmake -G Ninja -B "$LIBCXX_DIR" \
239+
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \
240+
-DCMAKE_BUILD_TYPE=Release \
241+
-DLLVM_USE_SANITIZER=Thread \
242+
-DCMAKE_C_COMPILER="/usr/bin/clang-${LLVM_VERSION}" \
243+
-DCMAKE_CXX_COMPILER="/usr/bin/clang++-${LLVM_VERSION}" \
244+
-DLLVM_TARGETS_TO_BUILD=Native \
245+
-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \
246+
-DLIBCXX_INCLUDE_TESTS=OFF \
247+
-DLIBCXXABI_INCLUDE_TESTS=OFF \
248+
-DLIBUNWIND_INCLUDE_TESTS=OFF \
249+
-DLIBCXXABI_USE_LLVM_UNWINDER=OFF \
250+
-S /tmp/llvm-project/runtimes
251+
grep -E 'CMAKE_(C|CXX)_COMPILER' "$LIBCXX_DIR/CMakeCache.txt"
252+
ninja -C "$LIBCXX_DIR" "-j$(nproc)" -v
253+
rm -rf /tmp/llvm-project
254+
255+
- name: Determine host
256+
id: host
257+
run: echo "host=$(./depends/config.guess)" >> "$GITHUB_OUTPUT"
258+
259+
- name: Restore depends cache
260+
id: depends-cache
261+
uses: actions/cache/restore@v4
262+
with:
263+
path: |
264+
depends/built
265+
depends/${{ steps.host.outputs.host }}
266+
key: depends-tsan-${{ hashFiles('depends/packages/*.mk') }}-${{ env.LLVM_VERSION }}
267+
268+
- name: Build depends (stage 1, without IPC)
269+
if: steps.depends-cache.outputs.cache-hit != 'true'
270+
run: |
271+
make -C depends "-j$(nproc)" \
272+
CC=clang \
273+
CXX=clang++ \
274+
CXXFLAGS="${LIBCXX_FLAGS}" \
275+
NO_QT=1 \
276+
NO_ZMQ=1 \
277+
NO_USDT=1 \
278+
NO_QR=1 \
279+
NO_IPC=1
280+
281+
- name: Save depends cache
282+
uses: actions/cache/save@v4
283+
if: steps.depends-cache.outputs.cache-hit != 'true'
284+
with:
285+
path: |
286+
depends/built
287+
depends/${{ steps.host.outputs.host }}
288+
key: depends-tsan-${{ hashFiles('depends/packages/*.mk') }}-${{ env.LLVM_VERSION }}
289+
290+
- name: Checkout libmultiprocess
291+
uses: actions/checkout@v4
292+
with:
293+
path: _libmultiprocess
294+
295+
- name: Replace libmultiprocess subtree
296+
run: |
297+
rm -rf src/ipc/libmultiprocess
298+
mv _libmultiprocess src/ipc/libmultiprocess
299+
300+
- name: Build depends (stage 2, IPC packages including libmultiprocess)
301+
run: |
302+
make -C depends "-j$(nproc)" \
303+
CC=clang \
304+
CXX=clang++ \
305+
CXXFLAGS="${LIBCXX_FLAGS}" \
306+
NO_QT=1 \
307+
NO_ZMQ=1 \
308+
NO_USDT=1 \
309+
NO_QR=1
310+
311+
- name: Restore ccache
312+
id: ccache-restore
313+
uses: actions/cache/restore@v4
314+
with:
315+
path: ${{ env.CCACHE_DIR }}
316+
key: ccache-TSan-${{ github.ref }}-${{ github.sha }}
317+
restore-keys: |
318+
ccache-TSan-${{ github.ref }}-
319+
ccache-TSan-
320+
321+
- name: CMake configure
322+
run: |
323+
cmake -S . -B build \
324+
--preset=dev-mode \
325+
-DCMAKE_BUILD_TYPE=Debug \
326+
-DBUILD_GUI=OFF \
327+
-DBUILD_GUI_TESTS=OFF \
328+
-DWITH_ZMQ=OFF \
329+
-DWITH_USDT=OFF \
330+
-DBUILD_BENCH=OFF \
331+
-DBUILD_FUZZ_BINARY=OFF \
332+
-DWITH_QRENCODE=OFF \
333+
-DSANITIZERS=thread \
334+
-DAPPEND_CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKCONTENTION -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES' \
335+
-DCMAKE_TOOLCHAIN_FILE=depends/${{ steps.host.outputs.host }}/toolchain.cmake \
336+
-G Ninja
337+
338+
- name: Build
339+
run: cmake --build build "-j$(nproc)"
340+
341+
- name: Run IPC and miner unit tests
342+
run: |
343+
ctest --test-dir build --tests-regex "ipc|miner_tests" --output-on-failure --timeout 480
344+
345+
- name: Run IPC functional tests
346+
run: |
347+
tests=()
348+
for _ in $(seq 1 20); do
349+
tests+=(interface_ipc)
350+
done
351+
LD_LIBRARY_PATH="depends/${{ steps.host.outputs.host }}/lib" \
352+
build/test/functional/test_runner.py -j "$(nproc)" "${tests[@]}" --timeout-factor=10
353+
354+
- name: Save ccache
355+
uses: actions/cache/save@v4
356+
if: github.ref == 'refs/heads/master' || steps.ccache-restore.outputs.cache-hit != 'true'
357+
with:
358+
path: ${{ env.CCACHE_DIR }}
359+
key: ccache-TSan-${{ github.ref }}-${{ github.sha }}

0 commit comments

Comments
 (0)