Skip to content

Commit ba70b85

Browse files
corona10vstinner
authored andcommitted
pythongh-112536: Add TSAN builds on Github Actions (python#116872)
1 parent 4caf512 commit ba70b85

File tree

6 files changed

+93
-0
lines changed

6 files changed

+93
-0
lines changed

.github/workflows/build.yml

+22
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,24 @@ jobs:
484484
- name: Tests
485485
run: xvfb-run make test
486486

487+
build_tsan:
488+
name: 'Thread sanitizer'
489+
needs: check_source
490+
if: needs.check_source.outputs.run_tests == 'true'
491+
uses: ./.github/workflows/reusable-tsan.yml
492+
with:
493+
config_hash: ${{ needs.check_source.outputs.config_hash }}
494+
options: ./configure --config-cache --with-thread-sanitizer --with-pydebug
495+
496+
build_tsan_free_threading:
497+
name: 'Thread sanitizer (free-threading)'
498+
needs: check_source
499+
if: needs.check_source.outputs.run_tests == 'true'
500+
uses: ./.github/workflows/reusable-tsan.yml
501+
with:
502+
config_hash: ${{ needs.check_source.outputs.config_hash }}
503+
options: ./configure --config-cache --disable-gil --with-thread-sanitizer --with-pydebug
504+
487505
# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
488506
cifuzz:
489507
name: CIFuzz
@@ -542,6 +560,8 @@ jobs:
542560
- build_windows_free_threading
543561
- test_hypothesis
544562
- build_asan
563+
- build_tsan
564+
- build_tsan_free_threading
545565
- cifuzz
546566

547567
runs-on: ubuntu-latest
@@ -575,6 +595,8 @@ jobs:
575595
build_windows,
576596
build_windows_free_threading,
577597
build_asan,
598+
build_tsan,
599+
build_tsan_free_threading,
578600
'
579601
|| ''
580602
}}

.github/workflows/reusable-tsan.yml

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
config_hash:
5+
required: true
6+
type: string
7+
options:
8+
required: true
9+
type: string
10+
11+
jobs:
12+
build_tsan_reusable:
13+
name: 'Thread sanitizer'
14+
runs-on: ubuntu-22.04
15+
timeout-minutes: 60
16+
steps:
17+
- uses: actions/checkout@v4
18+
- name: Runner image version
19+
run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV
20+
- name: Restore config.cache
21+
uses: actions/cache@v4
22+
with:
23+
path: config.cache
24+
key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }}
25+
- name: Install Dependencies
26+
run: |
27+
sudo ./.github/workflows/posix-deps-apt.sh
28+
sudo apt install -y clang
29+
# Reduce ASLR to avoid TSAN crashing
30+
sudo sysctl -w vm.mmap_rnd_bits=28
31+
- name: TSAN Option Setup
32+
run: |
33+
echo "TSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/Tools/tsan/supressions.txt" >> $GITHUB_ENV
34+
echo "CC=clang" >> $GITHUB_ENV
35+
echo "CXX=clang++" >> $GITHUB_ENV
36+
- name: Add ccache to PATH
37+
run: |
38+
echo "PATH=/usr/lib/ccache:$PATH" >> $GITHUB_ENV
39+
- name: Configure ccache action
40+
uses: hendrikmuhs/ccache-action@v1.2
41+
with:
42+
save: ${{ github.event_name == 'push' }}
43+
max-size: "200M"
44+
- name: Configure CPython
45+
run: ${{ inputs.options }}
46+
- name: Build CPython
47+
run: make -j4
48+
- name: Display build info
49+
run: make pythoninfo
50+
- name: Tests
51+
run: ./python -m test --tsan -j4

Lib/test/test_concurrent_futures/util.py

+4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def get_context(self):
8585
self.skipTest("ProcessPoolExecutor unavailable on this system")
8686
if sys.platform == "win32":
8787
self.skipTest("require unix system")
88+
if support.check_sanitizer(thread=True):
89+
self.skipTest("TSAN doesn't support threads after fork")
8890
return super().get_context()
8991

9092

@@ -111,6 +113,8 @@ def get_context(self):
111113
self.skipTest("ProcessPoolExecutor unavailable on this system")
112114
if sys.platform == "win32":
113115
self.skipTest("require unix system")
116+
if support.check_sanitizer(thread=True):
117+
self.skipTest("TSAN doesn't support threads after fork")
114118
return super().get_context()
115119

116120

Lib/test/test_logging.py

+4
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@
8080
skip_if_asan_fork = unittest.skipIf(
8181
support.HAVE_ASAN_FORK_BUG,
8282
"libasan has a pthread_create() dead lock related to thread+fork")
83+
skip_if_tsan_fork = unittest.skipIf(
84+
support.check_sanitizer(thread=True),
85+
"TSAN doesn't support threads after fork")
8386

8487

8588
class BaseTest(unittest.TestCase):
@@ -731,6 +734,7 @@ def remove_loop(fname, tries):
731734
@support.requires_fork()
732735
@threading_helper.requires_working_threading()
733736
@skip_if_asan_fork
737+
@skip_if_tsan_fork
734738
def test_post_fork_child_no_deadlock(self):
735739
"""Ensure child logging locks are not held; bpo-6721 & bpo-36533."""
736740
class _OurHandler(logging.Handler):

Lib/test/test_threading.py

+8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ def skip_unless_reliable_fork(test):
5050
return test
5151

5252

53+
skip_if_tsan_fork = unittest.skipIf(
54+
support.check_sanitizer(thread=True),
55+
"TSAN doesn't support threads after fork")
56+
57+
5358
def requires_subinterpreters(meth):
5459
"""Decorator to skip a test if subinterpreters are not supported."""
5560
return unittest.skipIf(interpreters is None,
@@ -634,6 +639,7 @@ def test_daemon_param(self):
634639
self.assertTrue(t.daemon)
635640

636641
@skip_unless_reliable_fork
642+
@skip_if_tsan_fork
637643
def test_dummy_thread_after_fork(self):
638644
# Issue #14308: a dummy thread in the active list doesn't mess up
639645
# the after-fork mechanism.
@@ -703,6 +709,7 @@ def f():
703709

704710
@skip_unless_reliable_fork
705711
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
712+
@skip_if_tsan_fork
706713
def test_main_thread_after_fork(self):
707714
code = """if 1:
708715
import os, threading
@@ -1271,6 +1278,7 @@ def test_2_join_in_forked_process(self):
12711278
self._run_and_join(script)
12721279

12731280
@skip_unless_reliable_fork
1281+
@skip_if_tsan_fork
12741282
def test_3_join_in_forked_from_thread(self):
12751283
# Like the test above, but fork() was called from a worker thread
12761284
# In the forked process, the main Thread object must be marked as stopped.

Python/thread_pthread.h

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@
9595
#endif
9696
#endif
9797

98+
/* Thread sanitizer doesn't currently support sem_clockwait */
99+
#ifdef _Py_THREAD_SANITIZER
100+
#undef HAVE_SEM_CLOCKWAIT
101+
#endif
98102

99103
/* Whether or not to use semaphores directly rather than emulating them with
100104
* mutexes and condition variables:

0 commit comments

Comments
 (0)