Add `'subint'` spawn backend scaffold (#379)
Land the scaffolding for a future sub-interpreter (PEP 734
`concurrent.interpreters`) actor spawn backend per issue #379. The
spawn flow itself is not yet implemented; `subint_proc()` raises a
placeholder `NotImplementedError` pointing at the tracking issue —
this commit only wires up the registry, the py-version gate, and
the harness.
Deats,
- bump `pyproject.toml` `requires-python` to `>=3.12, <3.15` and
list the `3.14` classifier — the new stdlib
`concurrent.interpreters` module only ships on 3.14
- extend `SpawnMethodKey = Literal[..., 'subint']`
- `try_set_start_method('subint')` grows a new `match` arm that
feature-detects the stdlib module and raises `RuntimeError` with
a clear banner on py<3.14
- `_methods` registers the new `subint_proc()` via the same
bottom-of-module late-import pattern used for `._trio` / `._mp`
Also,
- new `tractor/spawn/_subint.py` — top-level `try: from concurrent
import interpreters` guards `_has_subints: bool`; `subint_proc()`
signature mirrors `trio_proc`/`mp_proc` so the Phase B.2 impl can
drop in without touching the registry
- re-add `import sys` to `_spawn.py` (needed for the py-version msg
in the gate-error)
- `_testing.pytest.pytest_configure` wraps `try_set_start_method()`
in a `pytest.UsageError` handler so `--spawn-backend=subint` on
py<3.14 prints a clean banner instead of a traceback
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
(cherry picked from commit d318f1f8f4)
(MTF-only portion: kept tractor/spawn/_spawn.py tractor/spawn/_subint.py)
parent
7387bde030
commit
c3204a122a
|
|
@ -22,6 +22,7 @@ over multiple backends.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import platform
|
import platform
|
||||||
|
import sys
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
|
|
@ -64,6 +65,7 @@ SpawnMethodKey = Literal[
|
||||||
'trio', # supported on all platforms
|
'trio', # supported on all platforms
|
||||||
'mp_spawn',
|
'mp_spawn',
|
||||||
'mp_forkserver', # posix only
|
'mp_forkserver', # posix only
|
||||||
|
'subint', # py3.14+ via `concurrent.interpreters` (PEP 734)
|
||||||
]
|
]
|
||||||
_spawn_method: SpawnMethodKey = 'trio'
|
_spawn_method: SpawnMethodKey = 'trio'
|
||||||
|
|
||||||
|
|
@ -116,6 +118,17 @@ def try_set_start_method(
|
||||||
case 'trio':
|
case 'trio':
|
||||||
_ctx = None
|
_ctx = None
|
||||||
|
|
||||||
|
case 'subint':
|
||||||
|
# subints need no `mp.context`; feature-gate 3.14+
|
||||||
|
from ._subint import _has_subints
|
||||||
|
if not _has_subints:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'Spawn method {key!r} requires Python 3.14+ '
|
||||||
|
f'(stdlib `concurrent.interpreters`, PEP 734).\n'
|
||||||
|
f'Current runtime: {sys.version}'
|
||||||
|
)
|
||||||
|
_ctx = None
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'Spawn method `{key}` is invalid!\n'
|
f'Spawn method `{key}` is invalid!\n'
|
||||||
|
|
@ -465,6 +478,7 @@ async def new_proc(
|
||||||
# `hard_kill`/`proc_waiter` from this module.
|
# `hard_kill`/`proc_waiter` from this module.
|
||||||
from ._trio import trio_proc
|
from ._trio import trio_proc
|
||||||
from ._mp import mp_proc
|
from ._mp import mp_proc
|
||||||
|
from ._subint import subint_proc
|
||||||
|
|
||||||
|
|
||||||
# proc spawning backend target map
|
# proc spawning backend target map
|
||||||
|
|
@ -472,4 +486,5 @@ _methods: dict[SpawnMethodKey, Callable] = {
|
||||||
'trio': trio_proc,
|
'trio': trio_proc,
|
||||||
'mp_spawn': mp_proc,
|
'mp_spawn': mp_proc,
|
||||||
'mp_forkserver': mp_proc,
|
'mp_forkserver': mp_proc,
|
||||||
|
'subint': subint_proc,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
# tractor: structured concurrent "actors".
|
||||||
|
# Copyright 2018-eternity Tyler Goodlet.
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Sub-interpreter (`subint`) actor spawning backend.
|
||||||
|
|
||||||
|
Spawns each sub-actor as a CPython PEP 734 sub-interpreter
|
||||||
|
(`concurrent.interpreters.Interpreter`) — same-process state
|
||||||
|
isolation with faster start-up than an OS subproc, while
|
||||||
|
preserving tractor's IPC-based actor boundaries.
|
||||||
|
|
||||||
|
Availability
|
||||||
|
------------
|
||||||
|
Requires Python 3.14+ for the stdlib `concurrent.interpreters`
|
||||||
|
module. On older runtimes the module still imports (so the
|
||||||
|
registry stays introspectable) but `subint_proc()` raises.
|
||||||
|
|
||||||
|
Status
|
||||||
|
------
|
||||||
|
SCAFFOLDING STUB — `subint_proc()` is **not yet implemented**.
|
||||||
|
The real impl lands in Phase B.2 (see issue #379).
|
||||||
|
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
import sys
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
)
|
||||||
|
|
||||||
|
import trio
|
||||||
|
from trio import TaskStatus
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from concurrent import interpreters as _interpreters # type: ignore
|
||||||
|
_has_subints: bool = True
|
||||||
|
except ImportError:
|
||||||
|
_interpreters = None # type: ignore
|
||||||
|
_has_subints: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from tractor.discovery._addr import UnwrappedAddress
|
||||||
|
from tractor.runtime._portal import Portal
|
||||||
|
from tractor.runtime._runtime import Actor
|
||||||
|
from tractor.runtime._supervise import ActorNursery
|
||||||
|
|
||||||
|
|
||||||
|
async def subint_proc(
|
||||||
|
name: str,
|
||||||
|
actor_nursery: ActorNursery,
|
||||||
|
subactor: Actor,
|
||||||
|
errors: dict[tuple[str, str], Exception],
|
||||||
|
|
||||||
|
# passed through to actor main
|
||||||
|
bind_addrs: list[UnwrappedAddress],
|
||||||
|
parent_addr: UnwrappedAddress,
|
||||||
|
_runtime_vars: dict[str, Any], # serialized and sent to _child
|
||||||
|
*,
|
||||||
|
infect_asyncio: bool = False,
|
||||||
|
task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED,
|
||||||
|
proc_kwargs: dict[str, any] = {}
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Create a new sub-actor hosted inside a PEP 734
|
||||||
|
sub-interpreter running in a dedicated OS thread,
|
||||||
|
reusing tractor's existing UDS/TCP IPC handshake
|
||||||
|
for parent<->child channel setup.
|
||||||
|
|
||||||
|
NOT YET IMPLEMENTED — placeholder stub pending the
|
||||||
|
Phase B.2 impl.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if not _has_subints:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'The {"subint"!r} spawn backend requires Python 3.14+ '
|
||||||
|
f'(stdlib `concurrent.interpreters`, PEP 734).\n'
|
||||||
|
f'Current runtime: {sys.version}'
|
||||||
|
)
|
||||||
|
|
||||||
|
raise NotImplementedError(
|
||||||
|
'The `subint` spawn backend scaffolding is in place but '
|
||||||
|
'the spawn-flow itself is not yet implemented.\n'
|
||||||
|
'Tracking: https://github.com/goodboy/tractor/issues/379'
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue