Raise `subint` floor to py3.14 and split dep-groups
The private `_interpreters` C module ships since 3.13, but that vintage
wedges under our `threading.Thread` + multi-trio usage pattern
—> `_interpreters.exec()` silently never makes progress. 3.14 fixes it.
So gate on the presence of the public `concurrent.interpreters` wrapper
(3.14+ only) even tho we still call into the private module at runtime.
Deats,
- `try_set_start_method('subint')` error msg + `_subint` module
docstring/comments rewritten to document the 3.14 floor and why 3.13
can't work.
- `_subint._has_subints` gate now imports `concurrent.interpreters` (not
`_interpreters`) as the version sentinel.
Also, reshuffle `pyproject.toml` deps into
per-python-version `[tool.uv.dependency-groups]`:
- `subints` group: `msgspec>=0.21.0`, py>=3.14
- `eventfd` group: `cffi>=1.17.1`, py>=3.13,<3.14
- `sync_pause` group: `greenback`, py>=3.13,<3.14
(was in `devx`; moved out bc no 3.14 yet)
Bump top-level `msgspec>=0.20.0` too.
(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
(cherry picked from commit 34d9d482e4)
(MTF-only portion: kept tractor/spawn/_spawn.py tractor/spawn/_subint.py)
parent
b0420e34cc
commit
40e2916b8b
|
|
@ -120,16 +120,21 @@ def try_set_start_method(
|
||||||
|
|
||||||
case 'subint':
|
case 'subint':
|
||||||
# subints need no `mp.context`; feature-gate on the
|
# subints need no `mp.context`; feature-gate on the
|
||||||
# private `_interpreters` C module (available py3.13+
|
# py3.14 public `concurrent.interpreters` wrapper
|
||||||
# via cpython's internal stdlib — predates the PEP 734
|
# (PEP 734). We actually drive the private
|
||||||
# public wrapper which only lands in py3.14).
|
# `_interpreters` C module in legacy mode — see
|
||||||
|
# `tractor.spawn._subint` for why — but py3.13's
|
||||||
|
# vintage of that private module hangs under our
|
||||||
|
# multi-trio usage, so we refuse it via the public-
|
||||||
|
# module presence check.
|
||||||
from ._subint import _has_subints
|
from ._subint import _has_subints
|
||||||
if not _has_subints:
|
if not _has_subints:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'Spawn method {key!r} requires Python 3.13+ '
|
f'Spawn method {key!r} requires Python 3.14+.\n'
|
||||||
f'(private stdlib `_interpreters` C module; '
|
f'(On py3.13 the private `_interpreters` C '
|
||||||
f'the public `concurrent.interpreters` wrapper '
|
f'module exists but tractor\'s spawn flow '
|
||||||
f'lands in py3.14).\n'
|
f'wedges — see `tractor.spawn._subint` '
|
||||||
|
f'docstring for details.)\n'
|
||||||
f'Current runtime: {sys.version}'
|
f'Current runtime: {sys.version}'
|
||||||
)
|
)
|
||||||
_ctx = None
|
_ctx = None
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,17 @@ IPC-based actor boundary.
|
||||||
|
|
||||||
Availability
|
Availability
|
||||||
------------
|
------------
|
||||||
Runs on py3.13+ via the *private* stdlib `_interpreters` C
|
Requires Python **3.14+**. The private `_interpreters` C
|
||||||
module (which predates the py3.14 public
|
module we actually call into has shipped since 3.13, but
|
||||||
`concurrent.interpreters` stdlib wrapper). See the comment
|
that vintage has a latent bug in its thread/subint
|
||||||
above the `_interpreters` import below for the trade-offs
|
interaction which wedges tractor's spawn flow after
|
||||||
driving the private-API choice. On older runtimes the
|
`_interpreters.create()` — the driver `threading.Thread`
|
||||||
module still imports (so the registry stays
|
silently never makes progress inside `_interpreters.exec()`.
|
||||||
introspectable) but `subint_proc()` raises.
|
(Minimal standalone reproductions with threading +
|
||||||
|
`_interpreters.exec()` work fine on 3.13; only our
|
||||||
|
multi-trio-task usage triggers the hang. 3.14 fixes it.)
|
||||||
|
On older runtimes the module still imports (so the registry
|
||||||
|
stays introspectable) but `subint_proc()` raises.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -47,23 +51,31 @@ from trio import TaskStatus
|
||||||
|
|
||||||
|
|
||||||
# NOTE: we reach into the *private* `_interpreters` C module
|
# NOTE: we reach into the *private* `_interpreters` C module
|
||||||
# rather than `concurrent.interpreters`' public API because the
|
# for the actual subint create/exec/destroy calls rather than
|
||||||
# public API only exposes PEP 734's `'isolated'` config
|
# `concurrent.interpreters`' public API because the public API
|
||||||
# (per-interp GIL). Under `'isolated'`, any C extension missing
|
# only exposes PEP 734's `'isolated'` config (per-interp GIL).
|
||||||
# the `Py_mod_multiple_interpreters` slot (PEP 684) refuses to
|
# Under `'isolated'`, any C extension missing the
|
||||||
|
# `Py_mod_multiple_interpreters` slot (PEP 684) refuses to
|
||||||
# import; in our stack that's `msgspec` — which tractor uses
|
# import; in our stack that's `msgspec` — which tractor uses
|
||||||
# pervasively in the IPC layer — so isolated-mode subints can't
|
# pervasively in the IPC layer — so isolated-mode subints
|
||||||
# finish booting the sub-actor's `trio.run()`. msgspec PEP 684
|
# can't finish booting the sub-actor's `trio.run()`. msgspec
|
||||||
# support is open upstream at jcrist/msgspec#563.
|
# PEP 684 support is open upstream at jcrist/msgspec#563.
|
||||||
#
|
#
|
||||||
# Dropping to the `'legacy'` config keeps the main GIL + lets
|
# Dropping to the `'legacy'` config keeps the main GIL + lets
|
||||||
# existing C extensions load normally while preserving the
|
# existing C extensions load normally while preserving the
|
||||||
# state isolation we actually care about for the actor model
|
# state isolation we actually care about for the actor model
|
||||||
# (separate `sys.modules` / `__main__` / globals). Side win:
|
# (separate `sys.modules` / `__main__` / globals).
|
||||||
# the private `_interpreters` module has shipped since py3.13
|
#
|
||||||
# (it predates the PEP 734 stdlib landing), so the `subint`
|
# But — we feature-gate on the **public** `concurrent.interpreters`
|
||||||
# backend can run on py3.13+ despite `concurrent.interpreters`
|
# module (3.14+) even though we only call into the private
|
||||||
# itself being 3.14+.
|
# `_interpreters` module. Reason: the private module has
|
||||||
|
# shipped since 3.13, but the thread/subint interactions
|
||||||
|
# tractor relies on (`threading.Thread` driving
|
||||||
|
# `_interpreters.exec(..., legacy)` while a trio loop runs in
|
||||||
|
# the parent + another inside the subint + IPC between them)
|
||||||
|
# hang silently on 3.13 and only work cleanly on 3.14. See
|
||||||
|
# docstring above for the empirical details. Using the public
|
||||||
|
# module's existence as the gate keeps this check honest.
|
||||||
#
|
#
|
||||||
# Migration path: when msgspec (jcrist/msgspec#563) and any
|
# Migration path: when msgspec (jcrist/msgspec#563) and any
|
||||||
# other PEP 684-holdout C deps opt-in, we can switch to the
|
# other PEP 684-holdout C deps opt-in, we can switch to the
|
||||||
|
|
@ -85,6 +97,11 @@ from trio import TaskStatus
|
||||||
# - msgspec PEP 684 upstream tracker:
|
# - msgspec PEP 684 upstream tracker:
|
||||||
# https://github.com/jcrist/msgspec/issues/563
|
# https://github.com/jcrist/msgspec/issues/563
|
||||||
try:
|
try:
|
||||||
|
# gate: presence of the public 3.14 stdlib wrapper (we
|
||||||
|
# don't actually use it below, see NOTE above).
|
||||||
|
from concurrent import interpreters as _public_interpreters # noqa: F401 # type: ignore
|
||||||
|
# actual driver: the private C module (also present on
|
||||||
|
# 3.13 but we refuse that version — see gate above).
|
||||||
import _interpreters # type: ignore
|
import _interpreters # type: ignore
|
||||||
_has_subints: bool = True
|
_has_subints: bool = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -163,9 +180,10 @@ async def subint_proc(
|
||||||
'''
|
'''
|
||||||
if not _has_subints:
|
if not _has_subints:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'The {"subint"!r} spawn backend requires Python 3.13+ '
|
f'The {"subint"!r} spawn backend requires Python 3.14+.\n'
|
||||||
f'(private stdlib `_interpreters` C module; the public '
|
f'(On py3.13 the private `_interpreters` C module '
|
||||||
f'`concurrent.interpreters` wrapper lands in py3.14).\n'
|
f'exists but tractor\'s spawn flow wedges — see '
|
||||||
|
f'`tractor.spawn._subint` docstring for details.)\n'
|
||||||
f'Current runtime: {sys.version}'
|
f'Current runtime: {sys.version}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue