Add `subint_forkserver_proc` stub, flip dispatch, prune
Reduce `_subint_forkserver.py` to its variant-2 placeholder shape: - Add `subint_forkserver_proc` async stub raising `NotImplementedError` with a redirect msg pointing at the working variant-1 backend (`main_thread_forkserver`), jcrist/msgspec#1026 (upstream PEP 684 blocker), and #379 (subint umbrella). - `tractor.spawn._spawn._methods['subint_forkserver']` now dispatches to the stub instead of aliasing the variant-1 coroutine — `--spawn-backend=subint_forkserver` errors cleanly. - Drop now-dead module-scope: `ChildSigintMode` / `_DEFAULT_CHILD_SIGINT` defs, `_has_subints` try/except (replaced with import from `._subint`), unused imports (`partial`, `Literal`, `sys`, msgtypes/pretty_struct, `current_actor`, `cancel_on_completion`/`soft_kill`, `_server` TYPE_CHECKING). - Backward-compat re-exports of fork primitives kept until the follow-up commit migrates external test imports. - `tests/spawn/test_subint_forkserver.py::forkserver_spawn_method` fixture: flip hardcoded `'subint_forkserver'` → `'main_thread_forkserver'` so the test still exercises the working backend (full file rename comes in the test-import migration commit). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codesubint_forkserver_backend
parent
57dae0e4a6
commit
5e83881f10
|
|
@ -283,8 +283,8 @@ async def _happy_path_forkserver(
|
||||||
def forkserver_spawn_method():
|
def forkserver_spawn_method():
|
||||||
'''
|
'''
|
||||||
Flip `tractor.spawn._spawn._spawn_method` to
|
Flip `tractor.spawn._spawn._spawn_method` to
|
||||||
`'subint_forkserver'` for the duration of a test, then
|
`'main_thread_forkserver'` for the duration of a test,
|
||||||
restore whatever was in place before (usually the
|
then restore whatever was in place before (usually the
|
||||||
session-level CLI choice, typically `'trio'`).
|
session-level CLI choice, typically `'trio'`).
|
||||||
|
|
||||||
Without this, other tests in the same session would
|
Without this, other tests in the same session would
|
||||||
|
|
@ -295,7 +295,7 @@ def forkserver_spawn_method():
|
||||||
'''
|
'''
|
||||||
prev_method: str = _spawn_mod._spawn_method
|
prev_method: str = _spawn_mod._spawn_method
|
||||||
prev_ctx = _spawn_mod._ctx
|
prev_ctx = _spawn_mod._ctx
|
||||||
try_set_start_method('subint_forkserver')
|
try_set_start_method('main_thread_forkserver')
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
|
|
|
||||||
|
|
@ -494,6 +494,7 @@ from ._mp import mp_proc
|
||||||
from ._subint import subint_proc
|
from ._subint import subint_proc
|
||||||
from ._subint_fork import subint_fork_proc
|
from ._subint_fork import subint_fork_proc
|
||||||
from ._main_thread_forkserver import main_thread_forkserver_proc
|
from ._main_thread_forkserver import main_thread_forkserver_proc
|
||||||
|
from ._subint_forkserver import subint_forkserver_proc
|
||||||
|
|
||||||
|
|
||||||
# proc spawning backend target map
|
# proc spawning backend target map
|
||||||
|
|
@ -517,10 +518,9 @@ _methods: dict[SpawnMethodKey, Callable] = {
|
||||||
# Variant-2 (future, reserved): same fork machinery but
|
# Variant-2 (future, reserved): same fork machinery but
|
||||||
# child enters a sub-interpreter to host its `trio.run()`
|
# child enters a sub-interpreter to host its `trio.run()`
|
||||||
# — gated on jcrist/msgspec#1026 unblocking PEP 684
|
# — gated on jcrist/msgspec#1026 unblocking PEP 684
|
||||||
# isolated-mode subints. Today aliases to the variant-1
|
# isolated-mode subints. Today the stub raises
|
||||||
# impl so `--spawn-backend=subint_forkserver` keeps
|
# `NotImplementedError` pointing at the variant-1 backend
|
||||||
# working; flipped to a `NotImplementedError` stub in a
|
# + upstream blocker. See
|
||||||
# follow-up commit. See
|
|
||||||
# `tractor.spawn._subint_forkserver`.
|
# `tractor.spawn._subint_forkserver`.
|
||||||
'subint_forkserver': main_thread_forkserver_proc,
|
'subint_forkserver': subint_forkserver_proc,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,12 +142,9 @@ See also
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
from functools import partial
|
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Literal,
|
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -155,78 +152,42 @@ import trio
|
||||||
from trio import TaskStatus
|
from trio import TaskStatus
|
||||||
|
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
from tractor.msg import (
|
from ._subint import _has_subints
|
||||||
types as msgtypes,
|
|
||||||
pretty_struct,
|
# Backward-compat re-exports of the fork primitives whose
|
||||||
)
|
# canonical home is now `_main_thread_forkserver`. Kept here
|
||||||
from tractor.runtime._state import current_actor
|
# transiently so existing
|
||||||
from tractor.runtime._portal import Portal
|
# `from tractor.spawn._subint_forkserver import ...` callsites
|
||||||
from ._spawn import (
|
# in the tests + the conc-anal smoketest keep resolving;
|
||||||
cancel_on_completion,
|
# dropped once a follow-up commit migrates those imports to
|
||||||
soft_kill,
|
# the new module.
|
||||||
)
|
|
||||||
# Lower-level fork primitives — see module docstring for the
|
|
||||||
# split rationale. `_subint_forkserver` builds tractor's
|
|
||||||
# subint-family spawn backend on top of these.
|
|
||||||
from ._main_thread_forkserver import (
|
from ._main_thread_forkserver import (
|
||||||
_close_inherited_fds as _close_inherited_fds,
|
_close_inherited_fds as _close_inherited_fds,
|
||||||
_format_child_exit as _format_child_exit,
|
_format_child_exit as _format_child_exit,
|
||||||
fork_from_worker_thread as fork_from_worker_thread,
|
fork_from_worker_thread as fork_from_worker_thread,
|
||||||
wait_child as wait_child,
|
wait_child as wait_child,
|
||||||
_ForkedProc,
|
_ForkedProc as _ForkedProc,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from tractor.discovery._addr import UnwrappedAddress
|
from tractor.discovery._addr import UnwrappedAddress
|
||||||
from tractor.ipc import (
|
from tractor.runtime._portal import Portal
|
||||||
_server,
|
|
||||||
)
|
|
||||||
from tractor.runtime._runtime import Actor
|
from tractor.runtime._runtime import Actor
|
||||||
from tractor.runtime._supervise import ActorNursery
|
from tractor.runtime._supervise import ActorNursery
|
||||||
|
|
||||||
|
# Private CPython subint API — used by `run_subint_in_worker_thread`
|
||||||
|
# below. Imported only when 3.14+ is detected (via `_has_subints`
|
||||||
|
# from `_subint`); on older runtimes the symbol is `None` and
|
||||||
|
# the function raises a clean `RuntimeError` on entry.
|
||||||
|
if _has_subints:
|
||||||
|
import _interpreters # type: ignore
|
||||||
|
else:
|
||||||
|
_interpreters = None # type: ignore
|
||||||
|
|
||||||
|
|
||||||
log = get_logger('tractor')
|
log = get_logger('tractor')
|
||||||
|
|
||||||
|
|
||||||
# Configurable child-side SIGINT handling for forkserver-spawned
|
|
||||||
# subactors. Threaded through `subint_forkserver_proc`'s
|
|
||||||
# `proc_kwargs` under the `'child_sigint'` key.
|
|
||||||
#
|
|
||||||
# - `'ipc'` (default, currently the only implemented mode):
|
|
||||||
# child has NO trio-level SIGINT handler — trio.run() is on
|
|
||||||
# the fork-inherited non-main thread, `signal.set_wakeup_fd()`
|
|
||||||
# is main-thread-only. Cancellation flows exclusively via
|
|
||||||
# the parent's `Portal.cancel_actor()` IPC path. Safe +
|
|
||||||
# deterministic for nursery-structured apps where the parent
|
|
||||||
# is always the cancel authority. Known gap: orphan
|
|
||||||
# (post-parent-SIGKILL) children don't respond to SIGINT
|
|
||||||
# — see `test_orphaned_subactor_sigint_cleanup_DRAFT`.
|
|
||||||
#
|
|
||||||
# - `'trio'` (**not yet implemented**): install a manual
|
|
||||||
# SIGINT → trio-cancel bridge in the child's fork prelude
|
|
||||||
# (pre-`trio.run()`) so external Ctrl-C reaches stuck
|
|
||||||
# grandchildren even with a dead parent. Adds signal-
|
|
||||||
# handling surface the `'ipc'` default cleanly avoids; only
|
|
||||||
# pay for it when externally-interruptible children actually
|
|
||||||
# matter (e.g. CLI tool grandchildren).
|
|
||||||
ChildSigintMode = Literal['ipc', 'trio']
|
|
||||||
_DEFAULT_CHILD_SIGINT: ChildSigintMode = 'ipc'
|
|
||||||
|
|
||||||
|
|
||||||
# Feature-gate: py3.14+ via the public `concurrent.interpreters`
|
|
||||||
# wrapper. Matches the gate in `tractor.spawn._subint` —
|
|
||||||
# see that module's docstring for why we require the public
|
|
||||||
# API's presence even though we reach into the private
|
|
||||||
# `_interpreters` C module for actual calls.
|
|
||||||
try:
|
|
||||||
from concurrent import interpreters as _public_interpreters # noqa: F401 # type: ignore
|
|
||||||
import _interpreters # type: ignore
|
|
||||||
_has_subints: bool = True
|
|
||||||
except ImportError:
|
|
||||||
_interpreters = None # type: ignore
|
|
||||||
_has_subints: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
def run_subint_in_worker_thread(
|
def run_subint_in_worker_thread(
|
||||||
bootstrap: str,
|
bootstrap: str,
|
||||||
*,
|
*,
|
||||||
|
|
@ -308,3 +269,57 @@ def run_subint_in_worker_thread(
|
||||||
)
|
)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
|
async def subint_forkserver_proc(
|
||||||
|
name: str,
|
||||||
|
actor_nursery: ActorNursery,
|
||||||
|
subactor: Actor,
|
||||||
|
errors: dict[tuple[str, str], Exception],
|
||||||
|
|
||||||
|
bind_addrs: list[UnwrappedAddress],
|
||||||
|
parent_addr: UnwrappedAddress,
|
||||||
|
_runtime_vars: dict[str, Any],
|
||||||
|
*,
|
||||||
|
infect_asyncio: bool = False,
|
||||||
|
task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED,
|
||||||
|
proc_kwargs: dict[str, any] = {},
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
PLACEHOLDER — variant-2 (subint-isolated child runtime)
|
||||||
|
spawn-backend coroutine. Reserved for the eventual impl
|
||||||
|
that uses `run_subint_in_worker_thread()` in the fork-child
|
||||||
|
to host the child's `trio.run()` inside a fresh subint.
|
||||||
|
|
||||||
|
Today this stub raises immediately so
|
||||||
|
`--spawn-backend=subint_forkserver` errors cleanly with a
|
||||||
|
pointer to the working variant-1 backend
|
||||||
|
(`main_thread_forkserver`) and the upstream blocker
|
||||||
|
([jcrist/msgspec#1026](https://github.com/jcrist/msgspec/issues/1026)).
|
||||||
|
|
||||||
|
See this module's top-level docstring for the future-arch
|
||||||
|
design + what lives here when the variant-2 impl lands.
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise NotImplementedError(
|
||||||
|
f'`{ "subint_forkserver"!r}` spawn backend is reserved '
|
||||||
|
f'for the future variant-2 (subint-isolated child '
|
||||||
|
f'runtime) — gated on jcrist/msgspec#1026 unblocking '
|
||||||
|
f'PEP 684 isolated-mode subints upstream.\n'
|
||||||
|
f'\n'
|
||||||
|
f'For the working fork-based backend today, use '
|
||||||
|
f'`--spawn-backend=main_thread_forkserver` (variant '
|
||||||
|
f'1: fork from a regular main-interp worker thread, '
|
||||||
|
f'child runs trio on its own main interp).\n'
|
||||||
|
f'\n'
|
||||||
|
f'See:\n'
|
||||||
|
f' - tractor.spawn._main_thread_forkserver — the '
|
||||||
|
f'working variant-1 impl + design rationale\n'
|
||||||
|
f' - tractor.spawn._subint_forkserver — this '
|
||||||
|
f'module\'s docstring for the variant-2 future-arch\n'
|
||||||
|
f' - https://github.com/goodboy/tractor/issues/379 '
|
||||||
|
f'(subint umbrella)\n'
|
||||||
|
f' - https://github.com/jcrist/msgspec/issues/1026 '
|
||||||
|
f'(upstream PEP 684 blocker)'
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue