{"id":51187826,"url":"https://github.com/chidcgithub/threadcheck","last_synced_at":"2026-06-27T12:02:04.868Z","repository":{"id":363362669,"uuid":"1263022944","full_name":"ChidcGithub/Threadcheck","owner":"ChidcGithub","description":"Python data race detector for the free-threading (no-GIL) era.","archived":false,"fork":false,"pushed_at":"2026-06-08T15:09:05.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-08T16:26:37.973Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChidcGithub.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-08T14:46:47.000Z","updated_at":"2026-06-08T15:11:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ChidcGithub/Threadcheck","commit_stats":null,"previous_names":["chidcgithub/threadcheck"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ChidcGithub/Threadcheck","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChidcGithub%2FThreadcheck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChidcGithub%2FThreadcheck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChidcGithub%2FThreadcheck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChidcGithub%2FThreadcheck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChidcGithub","download_url":"https://codeload.github.com/ChidcGithub/Threadcheck/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChidcGithub%2FThreadcheck/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34852282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-27T12:02:04.075Z","updated_at":"2026-06-27T12:02:04.855Z","avatar_url":"https://github.com/ChidcGithub.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Threadcheck\n\n[![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org)\n[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n[![Platform](https://img.shields.io/badge/platform-linux%20%7C%20windows%20%7C%20macos-lightgrey)]()\n[![CI](https://github.com/ChidcGithub/Threadcheck/actions/workflows/test.yml/badge.svg)](https://github.com/ChidcGithub/Threadcheck/actions/workflows/test.yml)\n[![Ruff](https://img.shields.io/badge/code%20style-ruff-000000)](https://github.com/astral-sh/ruff)\n\nPython 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.\n\n---\n\n## Problem\n\nPython 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.\n\nthreadcheck is a pure-Python race detector that installs with `pip` and works out of the box across Linux, Windows, and macOS.\n\n---\n\n## Features\n\n- **Static analysis** -- scans AST for shared mutable state (`global`, `nonlocal`, class attributes, module-level lists/dicts) and missing lock protection\n- **Runtime detection** -- instruments code via AST transformation at import time; tracks memory accesses with vector clocks and detects happens-before violations (read-write and write-write races)\n- **Lock-aware suppression** -- understands `threading.Lock`/`RLock` and `with`-based synchronization; supports nested locks (`with lock1, lock2:`) with automatic per-lock vector clock tracking\n- **Cross-module analysis** -- two-pass scan collects `Thread(target=...)` and `executor.submit/map` targets across all files in a directory\n- **Confidence scoring** -- each warning tagged HIGH / MEDIUM / LOW based on thread context and lock coverage\n- **Configuration** -- `.threadcheckignore` file (gitignore-style patterns + `file:line` suppression) and `[tool.threadcheck]` section in `pyproject.toml`\n- **Multiple output formats** -- terminal (grouped by file, colorized), JSON, SARIF v2.1.0, and self-contained HTML report\n- **pytest plugin** -- automatic race detection during test execution via `--threadcheck` flag\n- **Free-threading compatibility checker** -- `threadcheck compat` scans installed packages for C extensions and checks FT ABI tags\n\n---\n\n## Installation\n\n```bash\npip install threadcheck\n```\n\nRequires Python 3.12+. Python 3.14+ is recommended for free-threading features.\n\n---\n\n## Quick Start\n\n### Static Analysis\n\nScan a file or directory for potential race conditions without running any code:\n\n```bash\nthreadcheck scan my_project/\n```\n\nOutput (grouped by file, with severity icons and per-file summary):\n\n```\n  [1/2] my_project/counter.py\n  --------------------------------\n    [!] HIGH [unsafe_global] line 8:8\n          Global variable `counter` modified without lock\n          suggestion: Use `threading.Lock()` to protect `counter`\n    [i] LOW [thread_usage] line 10:11\n          Thread creation detected (target=increment)\n\n  [2/2] my_project/worker.py\n  --------------------------------\n    [!] HIGH [shared_mutable] line 15:8\n          Module-level mutable object `results.append()` called from multiple threads\n\nTotal: 2 issue(s) in 2 file(s) (0 error(s), 2 warning(s), 0 info(s))\n```\n\n### Runtime Detection\n\nExecute a script with instrumentation to detect actual data races:\n\n```bash\nthreadcheck run my_script.py\n```\n\nOutput for a racing script:\n\n```\nData races detected:\n\n  [!] `counter`\n    |-- Thread-28928 (write) at my_script.py:8\n    |-- Thread-9888 (write) at my_script.py:8\n    \\-- No happens-before relationship between accesses\n       (10000 overlapping accesses)\n```\n\nA script protected with locks reports:\n\n```\nNo data races detected\n```\n\n### Free-threading Compatibility Check\n\nCheck whether your project's dependencies support free-threading:\n\n```bash\nthreadcheck compat\n```\n\nOutput:\n\n```\nthreadcheck compat - Free-threading compatibility check\nPython 3.13.10\n\n  [OK] numpy                 C extension has free-threading tag (cp313t-)\n  [??] torch                 C extension without free-threading tag\n  [OK] pytest                pure Python, no C extensions\n\nTotal: 3 package(s) - 2 compatible, 1 need verification, 0 not installed\n```\n\n### HTML Report\n\n```bash\nthreadcheck scan my_project/ -o report.html\n```\n\nGenerates a self-contained HTML report with dark/light theme, sortable table, and summary cards.\n\n### Quiet / Verbose Modes\n\n```bash\nthreadcheck scan my_project/ -q     # one-line summary only\nthreadcheck scan my_project/ -v     # include source code snippets\nthreadcheck scan my_project/        # default grouped output\n```\n\n---\n\n## Configuration\n\n### `.threadcheckignore`\n\nCreate a `.threadcheckignore` file in your project root (gitignore-style patterns):\n\n```\n# Ignore generated files\ngenerated/*.py\nbuild/*.py\n\n# Ignore specific lines in a specific file\nsrc/legacy.py:42          # suppress line 42\nsrc/legacy.py:50-60       # suppress lines 50-60\n\n# Negation (do not ignore)\n*.py\n!important.py\n```\n\n### `pyproject.toml`\n\n```toml\n[tool.threadcheck]\nignore = [\n    \"build/*\",\n    \"generated/*.py\",\n]\n```\n\nBoth sources are merged automatically.\n\n---\n\n## Output Formats\n\n| Flag | Format | Use Case |\n|---|---|---|\n| *(default)* | Terminal (colorized, grouped) | Interactive use |\n| `-q` | One-line summary | Quick status |\n| `-v` | Terminal + source snippets | Debugging |\n| `--json` | JSON | CI pipelines |\n| `--sarif` | SARIF v2.1.0 | GitHub CodeQL integration |\n| `-o report.html` | Self-contained HTML | Team reports |\n\n---\n\n## Commands\n\n| Command | Description |\n|---|---|\n| `scan \u003cpath\u003e` | Static race analysis of file or directory |\n| `run \u003cscript\u003e` | Execute script with runtime race detection |\n| `compat [path]` | Check free-threading compatibility of dependencies |\n\n---\n\n## Library Usage\n\n```python\nfrom threadcheck import analyze_file, analyze_path\n\nwarnings = analyze_file(\"my_module.py\")\nwarnings = analyze_path(\"src/\")\n\nfor w in warnings:\n    print(f\"{w.file}:{w.line} [{w.confidence.value}] {w.message}\")\n```\n\n```python\nfrom threadcheck.dynamic.tracker import ThreadCheckTracker\nfrom threadcheck.dynamic.transform import transform_and_compile\n\ncode = transform_and_compile(source, \"script.py\")\nThreadCheckTracker.start()\nexec(code, {\"_threadcheck_tracker\": ThreadCheckTracker})\nThreadCheckTracker.stop()\n\nprint(ThreadCheckTracker.format_races())\nThreadCheckTracker.reset()\n```\n\n### pytest Integration\n\n```bash\npython -m pytest tests/ --threadcheck\n```\n\nThe plugin hooks into `pytest_runtest_call` and reports race warnings as test failures.\n\n---\n\n## Architecture\n\n### Static Analysis Pipeline\n\n1. Parse source into AST\n2. Identify shared mutable state: globals, nonlocals, class attributes (`self.x`), module-level mutable objects (lists, dicts, sets)\n3. Detect thread creation sites (`threading.Thread`, `executor.submit/map`)\n4. Cross-reference with lock usage (`with lock:`, nested `with lock1, lock2:`)\n5. Assign confidence: HIGH (thread target, no lock), MEDIUM (thread present, no lock), LOW (suspicious pattern, no thread context)\n6. Report findings with repair suggestions\n\n### Runtime Detection Pipeline\n\n1. Parse source into AST\n2. Identify shared variables per function scope (`global`, `nonlocal` declarations)\n3. Transform AST: inject `read_before()` for reads and `write_before()` for writes of shared variables; inject `lock_acquire()`/`lock_release()` around `with` blocks\n4. Compile and execute transformed code under a tracker that maintains per-thread vector clocks\n5. On lock acquire, merge the lock's clock into the thread's clock (happens-before)\n6. On lock release, save the thread's clock to the lock\n7. After execution, scan access log for conflicting operations (concurrent writes OR write-read pairs with no happens-before relationship)\n8. Report detected races with thread IDs, source locations, and overlap counts\n\n### Free-threading Compatibility Check\n\n1. Parse project dependencies from `pyproject.toml` or `requirements.txt`\n2. For each installed package, scan for C extension files (`.pyd`/`.so`)\n3. If no C extensions found -\u003e COMPATIBLE\n4. If C extension filename contains free-threading ABI tag (`cp313t-`/`cpython-313t-`) -\u003e COMPATIBLE\n5. Otherwise -\u003e NEEDS_VERIFICATION\n\n---\n\n## Project Structure\n\n```\nthreadcheck/\n|-- pyproject.toml\n|-- src/threadcheck/\n|   |-- __init__.py\n|   |-- __main__.py\n|   |-- _version.py           # single version source\n|   |-- _tid.py               # platform thread ID (swapped per-platform in CI)\n|   |-- cli.py                # argument parsing + dispatch\n|   |-- config.py             # .threadcheckignore + pyproject.toml loader\n|   |-- pytest_plugin.py      # --threadcheck flag for pytest\n|   |-- static/\n|   |   |-- analyzer.py       # static analysis entry point\n|   |   |-- visitors.py       # 5 AST visitors\n|   |   |-- lock_tracker.py   # lock usage analysis\n|   |   \\-- models.py         # RaceWarning, Severity, Confidence\n|   |-- dynamic/\n|   |   |-- __main__.py       # run_script entry point\n|   |   |-- transform.py      # AST transformation engine\n|   |   |-- tracker.py        # runtime tracker with vector clocks\n|   |   |-- clock.py          # vector clock implementation\n|   |   \\-- hook.py           # sys.meta_path import hook\n|   |-- compat/\n|   |   |-- checker.py        # C extension FT tag scanner\n|   |   \\-- models.py         # FTCompatResult, CompatStatus\n|   \\-- reporting/\n|       |-- formatter.py      # terminal / JSON output\n|       |-- sarif.py          # SARIF v2.1.0 output\n|       \\-- html.py           # HTML report output\n|-- tests/\n|   |-- fixtures/             # 11 fixture files with known races\n|   |-- test_static_analyzer.py\n|   |-- test_dynamic_detector.py\n|   |-- test_formatter.py\n|   |-- test_sarif.py\n|   |-- test_compat.py\n|   |-- test_config.py\n|   \\-- test_pytest_plugin.py\n|-- demo/\n|   |-- race_example.py       # sample with intentional races\n|   \\-- run_demo.py           # demo runner for all output formats\n\\-- README.md\n```\n\n---\n\n## Platform Support\n\n| Platform | Python | CI |\n|---|---|---|\n| Linux (x86_64) | 3.12, 3.13, 3.14 | ubuntu-latest |\n| Windows (amd64) | 3.12, 3.13, 3.14 | windows-latest |\n| macOS (ARM64) | 3.12, 3.13, 3.14 | macos-latest |\n\nThread IDs: uses `native_id` (gettid) on Linux/macOS, `threading.get_ident()` on Windows.\n\n---\n\n## Roadmap\n\n- **v0.0.1.2** (current): Round A (core gap fill) + Round B (DX) -- static and dynamic analysis, lock tracking, cross-module analysis, pytest plugin, FT compat checker, HTML reports, configuration, enhanced output\n- **v0.2.0** (next): Round C -- `Thread.join()` happens-before, `threading.Atomic` support, function call chain tracking, deadlock detection\n- **v1.0.0** (future): Round D -- GitHub Action, pre-commit hook, VS Code integration, stable API\n\n---\n\n## Limitations\n\n- Static analysis may produce false positives (reports a race that cannot occur at runtime) and false negatives (misses races involving indirect sharing through aliases or containers)\n- Runtime detection modifies the AST before execution; code that introspects its own source or frame objects may behave differently\n- Runtime instrumentation incurs overhead (approximately 2-5x slowdown for typical code)\n- 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\n- Cross-module analysis handles `Thread(target=...)` and `executor.submit/map` but does not perform full inter-procedural data-flow analysis\n\n---\n\n## License\n\nMIT\n\n---\n\n## Contributing\n\nContributions are welcome. Please open an issue or submit a pull request on GitHub.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchidcgithub%2Fthreadcheck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchidcgithub%2Fthreadcheck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchidcgithub%2Fthreadcheck/lists"}