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():
|
||||
'''
|
||||
Flip `tractor.spawn._spawn._spawn_method` to
|
||||
`'subint_forkserver'` for the duration of a test, then
|
||||
restore whatever was in place before (usually the
|
||||
`'main_thread_forkserver'` for the duration of a test,
|
||||
then restore whatever was in place before (usually the
|
||||
session-level CLI choice, typically `'trio'`).
|
||||
|
||||
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_ctx = _spawn_mod._ctx
|
||||
try_set_start_method('subint_forkserver')
|
||||
try_set_start_method('main_thread_forkserver')
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -494,6 +494,7 @@ from ._mp import mp_proc
|
|||
from ._subint import subint_proc
|
||||
from ._subint_fork import subint_fork_proc
|
||||
from ._main_thread_forkserver import main_thread_forkserver_proc
|
||||
from ._subint_forkserver import subint_forkserver_proc
|
||||
|
||||
|
||||
# proc spawning backend target map
|
||||
|
|
@ -517,10 +518,9 @@ _methods: dict[SpawnMethodKey, Callable] = {
|
|||
# Variant-2 (future, reserved): same fork machinery but
|
||||
# child enters a sub-interpreter to host its `trio.run()`
|
||||
# — gated on jcrist/msgspec#1026 unblocking PEP 684
|
||||
# isolated-mode subints. Today aliases to the variant-1
|
||||
# impl so `--spawn-backend=subint_forkserver` keeps
|
||||
# working; flipped to a `NotImplementedError` stub in a
|
||||
# follow-up commit. See
|
||||
# isolated-mode subints. Today the stub raises
|
||||
# `NotImplementedError` pointing at the variant-1 backend
|
||||
# + upstream blocker. See
|
||||
# `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
|
||||
import sys
|
||||
import threading
|
||||
from functools import partial
|
||||
from typing import (
|
||||
Any,
|
||||
Literal,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
|
|
@ -155,78 +152,42 @@ import trio
|
|||
from trio import TaskStatus
|
||||
|
||||
from tractor.log import get_logger
|
||||
from tractor.msg import (
|
||||
types as msgtypes,
|
||||
pretty_struct,
|
||||
)
|
||||
from tractor.runtime._state import current_actor
|
||||
from tractor.runtime._portal import Portal
|
||||
from ._spawn import (
|
||||
cancel_on_completion,
|
||||
soft_kill,
|
||||
)
|
||||
# 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 ._subint import _has_subints
|
||||
|
||||
# Backward-compat re-exports of the fork primitives whose
|
||||
# canonical home is now `_main_thread_forkserver`. Kept here
|
||||
# transiently so existing
|
||||
# `from tractor.spawn._subint_forkserver import ...` callsites
|
||||
# in the tests + the conc-anal smoketest keep resolving;
|
||||
# dropped once a follow-up commit migrates those imports to
|
||||
# the new module.
|
||||
from ._main_thread_forkserver import (
|
||||
_close_inherited_fds as _close_inherited_fds,
|
||||
_format_child_exit as _format_child_exit,
|
||||
fork_from_worker_thread as fork_from_worker_thread,
|
||||
wait_child as wait_child,
|
||||
_ForkedProc,
|
||||
_ForkedProc as _ForkedProc,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tractor.discovery._addr import UnwrappedAddress
|
||||
from tractor.ipc import (
|
||||
_server,
|
||||
)
|
||||
from tractor.runtime._portal import Portal
|
||||
from tractor.runtime._runtime import Actor
|
||||
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')
|
||||
|
||||
|
||||
# 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(
|
||||
bootstrap: str,
|
||||
*,
|
||||
|
|
@ -308,3 +269,57 @@ def run_subint_in_worker_thread(
|
|||
)
|
||||
if err is not None:
|
||||
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