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_forkserver_backend
parent
09466a1e9d
commit
34d9d482e4
|
|
@ -44,11 +44,12 @@ dependencies = [
|
||||||
"tricycle>=0.4.1,<0.5",
|
"tricycle>=0.4.1,<0.5",
|
||||||
"wrapt>=1.16.0,<2",
|
"wrapt>=1.16.0,<2",
|
||||||
"colorlog>=6.8.2,<7",
|
"colorlog>=6.8.2,<7",
|
||||||
|
|
||||||
# built-in multi-actor `pdb` REPL
|
# built-in multi-actor `pdb` REPL
|
||||||
"pdbp>=1.8.2,<2", # windows only (from `pdbp`)
|
"pdbp>=1.8.2,<2", # windows only (from `pdbp`)
|
||||||
|
|
||||||
# typed IPC msging
|
# typed IPC msging
|
||||||
"msgspec>=0.20.0",
|
"msgspec>=0.20.0",
|
||||||
"cffi>=1.17.1",
|
|
||||||
"bidict>=0.23.1",
|
"bidict>=0.23.1",
|
||||||
"multiaddr>=0.2.0",
|
"multiaddr>=0.2.0",
|
||||||
"platformdirs>=4.4.0",
|
"platformdirs>=4.4.0",
|
||||||
|
|
@ -64,10 +65,13 @@ dev = [
|
||||||
]
|
]
|
||||||
devx = [
|
devx = [
|
||||||
# `tractor.devx` tooling
|
# `tractor.devx` tooling
|
||||||
"greenback>=1.2.1,<2", # TODO? 3.14 greenlet on nix?
|
|
||||||
"stackscope>=0.2.2,<0.3",
|
"stackscope>=0.2.2,<0.3",
|
||||||
# ^ requires this?
|
# ^ requires this?
|
||||||
"typing-extensions>=4.14.1",
|
"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 = [
|
testing = [
|
||||||
# test suite
|
# test suite
|
||||||
|
|
@ -85,6 +89,14 @@ repl = [
|
||||||
lint = [
|
lint = [
|
||||||
"ruff>=0.9.6"
|
"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
|
# TODO, add these with sane versions; were originally in
|
||||||
# `requirements-docs.txt`..
|
# `requirements-docs.txt`..
|
||||||
# docs = [
|
# docs = [
|
||||||
|
|
@ -93,6 +105,17 @@ lint = [
|
||||||
# ]
|
# ]
|
||||||
# ------ dependency-groups ------
|
# ------ 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]
|
[tool.uv.sources]
|
||||||
# XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)`
|
# XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)`
|
||||||
|
|
|
||||||
|
|
@ -117,16 +117,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