Skip to content

Feat: Free-threaded-safe TypedPython modules declare Py_MOD_GIL_NOT_USED (#159) - #209

Merged
b-re-w merged 3 commits into
developfrom
feat/tp-free-threaded
Oct 17, 2026
Merged

b-re-w merged 3 commits into
developfrom
feat/tp-free-threaded

Conversation

@b-re-w

@b-re-w b-re-w commented Oct 17, 2026

Copy link
Copy Markdown
Member

Closes #159

Defect fixed: importing any compiled TypedPython module on a free-threaded build turned the GIL back on for the whole process, because the module declared no Py_mod_gil slot. Without the GIL, the runtime's shared state also raced.

Feature added:

  • Generated modules declare {Py_mod_gil, Py_MOD_GIL_NOT_USED} (under Py_GIL_DISABLED).
  • Every piece of shared mutable state is classified (table in runtime/API.md, "Free-threaded CPython"):
    • thread-local: tp_depth, the poll countdown, tp_snapshot_depth;
    • atomic: the globals epoch, class guard tags and counters, and every tp_version_tag read;
    • immutable after init: the module state;
    • critical sections: list arguments of array parameters, held from copy-in to write-back on every exit path; the module dict for the epoch plus lookup and for the deopt counter.
  • Borrowed references that could race were replaced by strong ones (PyDict_GetItemRef, PyEval_GetFrameBuiltins).
  • A function with more than two list parameters stays interpreted with a reason, because CPython offers only one- and two-object critical sections.

Unchanged on purpose: functions holding an array snapshot still defer polls (#141), pending the decision in #152 on the changed-after-poll case. A long compiled array loop therefore delays other threads' stop-the-world until it returns. That is a liveness delay; results are unaffected.

Tests added: test_free_threaded.py, 23 tests.

  • What they check:
    • the module declares the slot;
    • the GIL stays off after import;
    • no GIL RuntimeWarning;
    • concurrency in child interpreters: private and shared lists, read-modify-write against a serialisation, concurrent FieldSet/FieldGet, class modification while reading, global rebinding with the watcher, deopt counting.
  • Red before the fix, under PYTHON_GIL=0: torn reads, a mis-rotated shared list, stale class bindings and lost deopt counts.
  • Built-in mutation tests: removing the array critical section, or the module-dict critical section, makes them fail.
  • TSan catches the epoch race but cannot see the list lock, because CPython's list code is not instrumented.

Tests changed:

  • A type refcount is skipped when CPython 3.15t reports a deferred value; instance refcounts are still exact.
  • A three-list sample became two lists.
  • The deferred-handler test allows a 5 ms margin.
  • tp_shim.c declares its GIL slot.

CI: typedpython.yml uses 3.15t in all three jobs. The Linux ASan job and the macOS app-fixture job have not run on 3.15t yet; they will run on the develop push.

Verification (local, CPython 3.15.0rc1 free-threaded, sys._is_gil_enabled() False): the full TypedPython suite with RuntimeWarning as error: 989 passed, 3 skipped. The branch also contains develop and #207.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant