Skip to main content

Data Race Detector for Free-Threading Python

Project description

threadcheck

Python License PyPI Tests

中文文档

Python data race detector for the free-threading (no-GIL) era. Detects concurrent access to shared mutable state in multi-threaded Python programs through static analysis and runtime instrumentation.


Problem

Python 3.14 (2026) introduces free-threading, removing the Global Interpreter Lock (GIL). This enables true parallel execution of multi-threaded code, but the ecosystem lacks debugging tools for concurrency bugs. Go has -race, C++ has ThreadSanitizer, Java has SpotBugs. Python has nothing comparable without recompiling the interpreter with Clang and TSan.

threadcheck is a pure-Python race detector that installs with pip and works out of the box.


Features

  • Static analysis -- scans AST for shared mutable state (global, nonlocal, class attributes) and missing lock protection
  • Runtime detection -- instruments code via AST transformation at import time; tracks memory accesses with vector clocks and detects happens-before violations
  • Lock-aware suppression -- understands threading.Lock, threading.RLock, and with-based synchronization; raises confidence when locks are missing and suppresses warnings when they are present
  • Confidence scoring -- each warning tagged HIGH / MEDIUM / LOW based on thread context and lock coverage
  • CLI tool -- single-command static scan or instrumented execution
  • JSON and SARIF output -- suitable for CI/CD pipeline integration

Installation

pip install threadcheck

Requires Python 3.12+. Python 3.14+ is recommended for free-threading features.


Quick Start

Static Analysis

Scan a file or directory for potential race conditions without running any code:

threadcheck scan my_project/

Output:

[WARNING] [HIGH] [unsafe_global] my_project/counter.py:8:8
       Global variable `counter` modified without lock in thread
       Suggestion: use `threading.Lock()` to protect access

JSON output:

threadcheck scan my_project/ --json -o report.json

Runtime Detection

Execute a script with instrumentation to detect actual data races:

threadcheck run my_script.py

Output for a racing script:

Data races detected:
  [!] `counter`
      Thread-28928 (write) at my_script.py:8
      Thread-9888 (write) at my_script.py:8

A script protected with locks reports:

No data races detected

CI Integration

pip install threadcheck
threadcheck scan src/ --json -o threadcheck_report.json

Commands

Command Description Status
scan <path> Static race analysis of file or directory Stable
run <script> Execute script with runtime race detection Beta
check-compat <path> Free-threading compatibility check Planned

Library Usage

from threadcheck import analyze_file, analyze_path

warnings = analyze_file("my_module.py")
warnings = analyze_path("src/")

for w in warnings:
    print(f"{w.file}:{w.line} [{w.confidence.value}] {w.message}")
from threadcheck.dynamic.tracker import ThreadCheckTracker
from threadcheck.dynamic.transform import transform_and_compile

code = transform_and_compile(source, "script.py")
ThreadCheckTracker.start()
exec(code, {"_threadcheck_tracker": ThreadCheckTracker})
ThreadCheckTracker.stop()

print(ThreadCheckTracker.format_races())
ThreadCheckTracker.reset()

Architecture

Static Analysis Pipeline

  1. Parse source into AST
  2. Identify shared mutable state: globals, nonlocals, class attributes (self.x), module-level mutable objects
  3. Detect thread creation sites (threading.Thread)
  4. Cross-reference with lock usage (with lock:, lock.acquire())
  5. Assign confidence: HIGH (thread target, no lock), MEDIUM (thread present, no lock), LOW (suspicious pattern, no thread context)
  6. Report findings with repair suggestions

Runtime Detection Pipeline

  1. Parse source into AST
  2. Identify shared variables per function scope
  3. Transform AST: inject write_before(), lock_acquire(), lock_release() calls around shared variable accesses
  4. Compile and execute transformed code under a tracker that maintains per-thread vector clocks
  5. On lock acquire, synchronize clocks (happens-before merge)
  6. After execution, scan access log for conflicting operations (concurrent writes or write-read pairs with no happens-before relationship)
  7. Report detected races with thread IDs and source locations

Project Structure

threadcheck/
├── pyproject.toml
├── src/
│   └── threadcheck/
│       ├── __init__.py
│       ├── __main__.py
│       ├── _version.py          # single version source
│       ├── cli.py               # argument parsing + dispatch
│       ├── static/
│       │   ├── analyzer.py      # static analysis entry point
│       │   ├── visitors.py      # AST visitors (global, nonlocal, class attr, shared mutable)
│       │   ├── lock_tracker.py  # lock usage analysis
│       │   └── models.py        # RaceWarning, Severity, Confidence models
│       ├── dynamic/
│       │   ├── __main__.py      # run_script entry point
│       │   ├── transform.py     # AST transformation engine
│       │   ├── tracker.py       # runtime tracker with vector clocks
│       │   ├── clock.py         # vector clock implementation
│       │   └── hook.py          # sys.meta_path import hook
│       ├── reporting/
│       │   ├── formatter.py     # terminal output formatting
│       │   └── types.py         # type re-exports
│       └── pytest_plugin.py     # pytest integration (planned)
├── tests/
│   ├── fixtures/                # sample code with known races
│   ├── test_static_analyzer.py
│   └── test_dynamic_detector.py
└── README.md / README_CN.md

Roadmap

Phase Feature Status
1 CLI, static analysis (globals/nonlocals) Done
2 Class attributes, lock suppression, confidence scoring Done
3 AST import hook, runtime instrumentation, vector clocks Done
4 Race report deduplication, enhanced happens-before analysis Planned
5 SARIF output, JSON reporting Planned
6 pytest plugin Planned
7 Free-threading compatibility checker Planned

Limitations

  • Static analysis may produce false positives (reports race that cannot occur at runtime) and false negatives (misses races that involve indirect sharing through aliases or containers)
  • Runtime detection modifies the AST before execution; code that introspects its own source or frame objects may behave differently
  • Runtime instrumentation incurs overhead (approximately 2-5x slowdown for typical code)
  • Lock tracking supports threading.Lock, threading.RLock, and standard with-based patterns; other synchronization primitives (threading.Event, threading.Condition, third-party libraries) are not tracked

License

MIT


Contributing

Contributions are welcome. Please open an issue or submit a pull request on GitHub.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

threadcheck-0.0.1.tar.gz (30.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

threadcheck-0.0.1-py3-none-any.whl (21.2 kB view details)

Uploaded Python 3

File details

Details for the file threadcheck-0.0.1.tar.gz.

File metadata

  • Download URL: threadcheck-0.0.1.tar.gz
  • Upload date:
  • Size: 30.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for threadcheck-0.0.1.tar.gz
Algorithm Hash digest
SHA256 79ed0355a91807b15e97f5d16107efe75f745d4f9bc263c94d5e1b0329701807
MD5 f2e8ab7d7e956e18022031a8583b99d4
BLAKE2b-256 edf6ea11e838d98b67efbdcf351f9aa10317eb1c50ccc3bc04aba5f4c6851895

See more details on using hashes here.

Provenance

The following attestation bundles were made for threadcheck-0.0.1.tar.gz:

Publisher: release.yml on ChidcGithub/Threadcheck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file threadcheck-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: threadcheck-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 21.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for threadcheck-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cc483be1dd5241f8062105277522502f7cb05a06d5028556079d903c072db100
MD5 cda3b2ac176729f7de478af44217814f
BLAKE2b-256 4741f07d130b8479c63c47316fa4864b5122bb387747a80d4699ad385fee037e

See more details on using hashes here.

Provenance

The following attestation bundles were made for threadcheck-0.0.1-py3-none-any.whl:

Publisher: release.yml on ChidcGithub/Threadcheck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page