Skip to content

Run the test suite on free-threaded CPython (3.14t) #102

Description

@bagowix

Problem

Thread safety is a documented property of the breaker: a single threading.Lock
guards the two await-free critical sections in interlock/_engine.py:117 and is
released before the protected callable runs. Nothing in CI exercises that claim.

  • The matrix in .github/workflows/ci.yml runs 3.11–3.14, all GIL-enabled.
  • The suite is parallelised by process (pytest-xdist, -n auto), never by
    thread, so no test drives one breaker from several threads at once.
  • Free-threaded CPython is officially supported as of 3.14, and it is exactly
    where a lock-scope assumption that happens to be protected by the GIL turns
    into a real race: the sliding-window mutation, the snapshot() copy
    (interlock/_engine.py:344), the auto-transition timer arm/cancel pair and
    the coordinator lane hand-off.

A library placed on every dependency call is the last place a latent data race
should be discovered by a user.

Proposal

Two independent changes:

  1. Add 3.14t to the CI matrix. astral-sh/setup-uv resolves free-threaded
    interpreters from the t suffix, so this is a matrix entry plus whatever
    fallout it exposes. Keep it non-blocking (continue-on-error) only until it
    is green — the point is to make it required.
  2. Add thread-concurrency tests with pytest-run-parallel (dev dependency,
    --parallel-threads=N), scoped to the paths where concurrent mutation is
    possible rather than the whole suite: test_breaker.py, test_registry.py,
    test_engine.py, test_coordination.py, test_observability.py.

Acceptance criteria

  • CI runs the suite on a free-threaded 3.14 interpreter and the job is required.
  • A marked subset runs each test from multiple threads and asserts the
    invariants that must survive contention: window counts add up, snapshot()
    never returns a torn view, HALF_OPEN probe caps are never exceeded, the
    registry hands out exactly one breaker per name.
  • The core stays dependency-free; pytest-run-parallel is dev-only.
  • Any race the new jobs expose is fixed or filed with a reproducer before the
    job is made required.
  • Document in CONTRIBUTING.md how to run the free-threaded and threaded
    variants locally.

Non-goals

  • ThreadSanitizer or other native tooling.
  • Making free-threaded builds a supported distribution target (no wheels, no
    classifier changes) — this is about verifying an existing correctness claim.
  • Performance comparisons between GIL and free-threaded runs; CodSpeed keeps
    measuring the default interpreter.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions