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
subint_spawner_backend
parent
dbe2e8bd82
commit
79390a4e3a
|
|
@ -44,11 +44,12 @@ dependencies = [
|
|||
"tricycle>=0.4.1,<0.5",
|
||||
"wrapt>=1.16.0,<2",
|
||||
"colorlog>=6.8.2,<7",
|
||||
|
||||
# built-in multi-actor `pdb` REPL
|
||||
"pdbp>=1.8.2,<2", # windows only (from `pdbp`)
|
||||
|
||||
# typed IPC msging
|
||||
"msgspec>=0.20.0",
|
||||
"cffi>=1.17.1",
|
||||
"bidict>=0.23.1",
|
||||
"multiaddr>=0.2.0",
|
||||
"platformdirs>=4.4.0",
|
||||
|
|
@ -64,10 +65,13 @@ dev = [
|
|||
]
|
||||
devx = [
|
||||
# `tractor.devx` tooling
|
||||
"greenback>=1.2.1,<2", # TODO? 3.14 greenlet on nix?
|
||||
"stackscope>=0.2.2,<0.3",
|
||||
# ^ requires this?
|
||||
"typing-extensions>=4.14.1",
|
||||
# {include-group = 'sync_pause'}, # XXX, no 3.14 yet!
|
||||
]
|
||||
sync_pause = [
|
||||
"greenback>=1.2.1,<2", # TODO? 3.14 greenlet on nix?
|
||||
]
|
||||
testing = [
|
||||
# test suite
|
||||
|
|
@ -85,6 +89,14 @@ repl = [
|
|||
lint = [
|
||||
"ruff>=0.9.6"
|
||||
]
|
||||
# XXX, used for linux-only hi perf eventfd+shm channels
|
||||
# now mostly moved over to `hotbaud`.
|
||||
eventfd = [
|
||||
"cffi>=1.17.1",
|
||||
]
|
||||
subints = [
|
||||
"msgspec>=0.21.0",
|
||||
]
|
||||
# TODO, add these with sane versions; were originally in
|
||||
# `requirements-docs.txt`..
|
||||
# docs = [
|
||||
|
|
@ -93,6 +105,17 @@ lint = [
|
|||
# ]
|
||||
# ------ dependency-groups ------
|
||||
|
||||
[tool.uv.dependency-groups]
|
||||
# for subints, we require 3.14+ due to 2 issues,
|
||||
# - hanging behaviour for various multi-task teardown cases (see
|
||||
# "Availability" section in the `tractor.spawn._subints` doc string).
|
||||
# - `msgspec` support which is oustanding per PEP 684 upstream tracker:
|
||||
# https://github.com/jcrist/msgspec/issues/563
|
||||
#
|
||||
# https://docs.astral.sh/uv/concepts/projects/dependencies/#group-requires-python
|
||||
subints = {requires-python = ">=3.14"}
|
||||
eventfd = {requires-python = ">=3.13, <3.14"}
|
||||
sync_pause = {requires-python = ">=3.13, <3.14"}
|
||||
|
||||
[tool.uv.sources]
|
||||
# XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)`
|
||||
|
|
|
|||
|
|
@ -117,16 +117,21 @@ def try_set_start_method(
|
|||
|
||||
case 'subint':
|
||||
# subints need no `mp.context`; feature-gate on the
|
||||
# private `_interpreters` C module (available py3.13+
|
||||
# via cpython's internal stdlib — predates the PEP 734
|
||||
# public wrapper which only lands in py3.14).
|
||||
# py3.14 public `concurrent.interpreters` wrapper
|
||||
# (PEP 734). We actually drive the private
|
||||
# `_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
|
||||
if not _has_subints:
|
||||
raise RuntimeError(
|
||||
f'Spawn method {key!r} requires Python 3.13+ '
|
||||
f'(private stdlib `_interpreters` C module; '
|
||||
f'the public `concurrent.interpreters` wrapper '
|
||||
f'lands in py3.14).\n'
|
||||
f'Spawn method {key!r} requires Python 3.14+.\n'
|
||||
f'(On py3.13 the private `_interpreters` C '
|
||||
f'module exists but tractor\'s spawn flow '
|
||||
f'wedges — see `tractor.spawn._subint` '
|
||||
f'docstring for details.)\n'
|
||||
f'Current runtime: {sys.version}'
|
||||
)
|
||||
_ctx = None
|
||||
|
|
|
|||
|
|
@ -25,13 +25,17 @@ IPC-based actor boundary.
|
|||
|
||||
Availability
|
||||
------------
|
||||
Runs on py3.13+ via the *private* stdlib `_interpreters` C
|
||||
module (which predates the py3.14 public
|
||||
`concurrent.interpreters` stdlib wrapper). See the comment
|
||||
above the `_interpreters` import below for the trade-offs
|
||||
driving the private-API choice. On older runtimes the
|
||||
module still imports (so the registry stays
|
||||
introspectable) but `subint_proc()` raises.
|
||||
Requires Python **3.14+**. The private `_interpreters` C
|
||||
module we actually call into has shipped since 3.13, but
|
||||
that vintage has a latent bug in its thread/subint
|
||||
interaction which wedges tractor's spawn flow after
|
||||
`_interpreters.create()` — the driver `threading.Thread`
|
||||
silently never makes progress inside `_interpreters.exec()`.
|
||||
(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
|
||||
|
|
@ -47,23 +51,31 @@ from trio import TaskStatus
|
|||
|
||||
|
||||
# NOTE: we reach into the *private* `_interpreters` C module
|
||||
# rather than `concurrent.interpreters`' public API because the
|
||||
# public API only exposes PEP 734's `'isolated'` config
|
||||
# (per-interp GIL). Under `'isolated'`, any C extension missing
|
||||
# the `Py_mod_multiple_interpreters` slot (PEP 684) refuses to
|
||||
# for the actual subint create/exec/destroy calls rather than
|
||||
# `concurrent.interpreters`' public API because the public API
|
||||
# only exposes PEP 734's `'isolated'` config (per-interp GIL).
|
||||
# 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
|
||||
# pervasively in the IPC layer — so isolated-mode subints can't
|
||||
# finish booting the sub-actor's `trio.run()`. msgspec PEP 684
|
||||
# support is open upstream at jcrist/msgspec#563.
|
||||
# pervasively in the IPC layer — so isolated-mode subints
|
||||
# can't finish booting the sub-actor's `trio.run()`. msgspec
|
||||
# PEP 684 support is open upstream at jcrist/msgspec#563.
|
||||
#
|
||||
# Dropping to the `'legacy'` config keeps the main GIL + lets
|
||||
# existing C extensions load normally while preserving the
|
||||
# state isolation we actually care about for the actor model
|
||||
# (separate `sys.modules` / `__main__` / globals). Side win:
|
||||
# the private `_interpreters` module has shipped since py3.13
|
||||
# (it predates the PEP 734 stdlib landing), so the `subint`
|
||||
# backend can run on py3.13+ despite `concurrent.interpreters`
|
||||
# itself being 3.14+.
|
||||
# (separate `sys.modules` / `__main__` / globals).
|
||||
#
|
||||
# But — we feature-gate on the **public** `concurrent.interpreters`
|
||||
# module (3.14+) even though we only call into the private
|
||||
# `_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
|
||||
# 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:
|
||||
# https://github.com/jcrist/msgspec/issues/563
|
||||
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
|
||||
_has_subints: bool = True
|
||||
except ImportError:
|
||||
|
|
@ -163,9 +180,10 @@ async def subint_proc(
|
|||
'''
|
||||
if not _has_subints:
|
||||
raise RuntimeError(
|
||||
f'The {"subint"!r} spawn backend requires Python 3.13+ '
|
||||
f'(private stdlib `_interpreters` C module; the public '
|
||||
f'`concurrent.interpreters` wrapper lands in py3.14).\n'
|
||||
f'The {"subint"!r} spawn backend requires Python 3.14+.\n'
|
||||
f'(On py3.13 the private `_interpreters` C module '
|
||||
f'exists but tractor\'s spawn flow wedges — see '
|
||||
f'`tractor.spawn._subint` docstring for details.)\n'
|
||||
f'Current runtime: {sys.version}'
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue