Migrate test/smoketest imports + rename test file
Rename `tests/spawn/test_subint_forkserver.py` → `test_main_thread_forkserver.py` and migrate its imports + internal refs to the new canonical names: - `fork_from_worker_thread`, `wait_child` → from `tractor.spawn._main_thread_forkserver`. - `run_subint_in_worker_thread` → still from `_subint_forkserver` (variant-2 primitive). - Module docstring + tier-3 fixture + the `*_spawn_basic` test fn renamed for variant-1-honesty. - Orphan-harness subprocess argv flipped from `'subint_forkserver'` → `'main_thread_forkserver'`. `ai/conc-anal/subint_fork_from_main_thread_smoketest.py` imports split the same way. `tractor/spawn/_subint_forkserver.py` drops the backward- compat re-exports of the fork primitives — the only consumers (test file + smoketest) now import from `_main_thread_forkserver` directly. (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
5e83881f10
commit
9f0709eee2
|
|
@ -89,11 +89,13 @@ except ImportError:
|
||||||
# the "zero tractor imports" isolation guarantee; now that
|
# the "zero tractor imports" isolation guarantee; now that
|
||||||
# CPython-level feasibility is confirmed, the validated
|
# CPython-level feasibility is confirmed, the validated
|
||||||
# primitives have moved into tractor proper.)
|
# primitives have moved into tractor proper.)
|
||||||
from tractor.spawn._subint_forkserver import (
|
from tractor.spawn._main_thread_forkserver import (
|
||||||
fork_from_worker_thread,
|
fork_from_worker_thread,
|
||||||
run_subint_in_worker_thread,
|
|
||||||
wait_child,
|
wait_child,
|
||||||
)
|
)
|
||||||
|
from tractor.spawn._subint_forkserver import (
|
||||||
|
run_subint_in_worker_thread,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
'''
|
'''
|
||||||
Integration exercises for the `tractor.spawn._subint_forkserver`
|
Integration exercises for the `tractor.spawn._main_thread_forkserver`
|
||||||
submodule at three tiers:
|
submodule at three tiers:
|
||||||
|
|
||||||
1. the low-level primitives
|
1. the low-level primitives
|
||||||
(`fork_from_worker_thread()` +
|
(`fork_from_worker_thread()` from `_main_thread_forkserver`
|
||||||
`run_subint_in_worker_thread()`) driven from inside a real
|
+ `run_subint_in_worker_thread()` from
|
||||||
|
`_subint_forkserver`) driven from inside a real
|
||||||
`trio.run()` in the parent process,
|
`trio.run()` in the parent process,
|
||||||
|
|
||||||
2. the full `subint_forkserver_proc` spawn backend wired
|
2. the full `main_thread_forkserver_proc` spawn backend wired
|
||||||
through tractor's normal actor-nursery + portal-RPC
|
through tractor's normal actor-nursery + portal-RPC
|
||||||
machinery — i.e. `open_root_actor` + `open_nursery` +
|
machinery — i.e. `open_root_actor` + `open_nursery` +
|
||||||
`run_in_actor` against a subactor spawned via fork from a
|
`run_in_actor` against a subactor spawned via fork from a
|
||||||
|
|
@ -27,15 +28,15 @@ Those smoke-test scenarios are standalone — no trio runtime
|
||||||
in the *parent*. Tiers (1)+(2) here cover the primitives
|
in the *parent*. Tiers (1)+(2) here cover the primitives
|
||||||
driven from inside `trio.run()` in the parent, and tier (3)
|
driven from inside `trio.run()` in the parent, and tier (3)
|
||||||
(the `*_spawn_basic` test) drives the registered
|
(the `*_spawn_basic` test) drives the registered
|
||||||
`subint_forkserver` spawn backend end-to-end against the
|
`main_thread_forkserver` spawn backend end-to-end against
|
||||||
tractor runtime.
|
the tractor runtime.
|
||||||
|
|
||||||
Gating
|
Gating
|
||||||
------
|
------
|
||||||
- py3.14+ (via `concurrent.interpreters` presence)
|
- py3.14+ (via `concurrent.interpreters` presence)
|
||||||
- no `--spawn-backend` restriction — the backend-level test
|
- no `--spawn-backend` restriction — the backend-level test
|
||||||
flips `tractor.spawn._spawn._spawn_method` programmatically
|
flips `tractor.spawn._spawn._spawn_method` programmatically
|
||||||
(via `try_set_start_method('subint_forkserver')`) and
|
(via `try_set_start_method('main_thread_forkserver')`) and
|
||||||
restores it on teardown, so these tests are independent of
|
restores it on teardown, so these tests are independent of
|
||||||
the session-level CLI backend choice.
|
the session-level CLI backend choice.
|
||||||
|
|
||||||
|
|
@ -64,11 +65,13 @@ from tractor.devx import dump_on_hang
|
||||||
# `tractor.spawn._subint` for why.
|
# `tractor.spawn._subint` for why.
|
||||||
pytest.importorskip('concurrent.interpreters')
|
pytest.importorskip('concurrent.interpreters')
|
||||||
|
|
||||||
from tractor.spawn._subint_forkserver import ( # noqa: E402
|
from tractor.spawn._main_thread_forkserver import ( # noqa: E402
|
||||||
fork_from_worker_thread,
|
fork_from_worker_thread,
|
||||||
run_subint_in_worker_thread,
|
|
||||||
wait_child,
|
wait_child,
|
||||||
)
|
)
|
||||||
|
from tractor.spawn._subint_forkserver import ( # noqa: E402
|
||||||
|
run_subint_in_worker_thread,
|
||||||
|
)
|
||||||
from tractor.spawn import _spawn as _spawn_mod # noqa: E402
|
from tractor.spawn import _spawn as _spawn_mod # noqa: E402
|
||||||
from tractor.spawn._spawn import try_set_start_method # noqa: E402
|
from tractor.spawn._spawn import try_set_start_method # noqa: E402
|
||||||
|
|
||||||
|
|
@ -195,7 +198,7 @@ def test_fork_from_worker_thread_via_trio(
|
||||||
deadline: float = 10.0
|
deadline: float = 10.0
|
||||||
with dump_on_hang(
|
with dump_on_hang(
|
||||||
seconds=deadline,
|
seconds=deadline,
|
||||||
path='/tmp/subint_forkserver_baseline.dump',
|
path='/tmp/main_thread_forkserver_baseline.dump',
|
||||||
):
|
):
|
||||||
pid: int = trio.run(
|
pid: int = trio.run(
|
||||||
partial(run_fork_in_non_trio_thread, deadline),
|
partial(run_fork_in_non_trio_thread, deadline),
|
||||||
|
|
@ -217,14 +220,14 @@ def test_fork_and_run_trio_in_child() -> None:
|
||||||
`trio.run()` inside it on yet another worker thread.
|
`trio.run()` inside it on yet another worker thread.
|
||||||
|
|
||||||
This is the full "forkserver + trio-in-subint-in-child"
|
This is the full "forkserver + trio-in-subint-in-child"
|
||||||
pattern the proposed `subint_forkserver` spawn backend
|
pattern the proposed `main_thread_forkserver` spawn backend
|
||||||
would rest on.
|
would rest on.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
deadline: float = 15.0
|
deadline: float = 15.0
|
||||||
with dump_on_hang(
|
with dump_on_hang(
|
||||||
seconds=deadline,
|
seconds=deadline,
|
||||||
path='/tmp/subint_forkserver_trio_in_child.dump',
|
path='/tmp/main_thread_forkserver_trio_in_child.dump',
|
||||||
):
|
):
|
||||||
pid: int = trio.run(
|
pid: int = trio.run(
|
||||||
partial(
|
partial(
|
||||||
|
|
@ -237,7 +240,7 @@ def test_fork_and_run_trio_in_child() -> None:
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# tier-3 backend test: drive the registered `subint_forkserver`
|
# tier-3 backend test: drive the registered `main_thread_forkserver`
|
||||||
# spawn backend end-to-end through tractor's actor-nursery +
|
# spawn backend end-to-end through tractor's actor-nursery +
|
||||||
# portal-RPC machinery.
|
# portal-RPC machinery.
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
|
|
@ -260,7 +263,7 @@ async def _happy_path_forkserver(
|
||||||
Parent-side harness: stand up a root actor, open an actor
|
Parent-side harness: stand up a root actor, open an actor
|
||||||
nursery, spawn one subactor via the currently-selected
|
nursery, spawn one subactor via the currently-selected
|
||||||
spawn backend (which this test will have flipped to
|
spawn backend (which this test will have flipped to
|
||||||
`subint_forkserver`), run a trivial RPC through its
|
`main_thread_forkserver`), run a trivial RPC through its
|
||||||
portal, assert the round-trip result.
|
portal, assert the round-trip result.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
@ -304,19 +307,19 @@ def forkserver_spawn_method():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(60, method='thread')
|
@pytest.mark.timeout(60, method='thread')
|
||||||
def test_subint_forkserver_spawn_basic(
|
def test_main_thread_forkserver_spawn_basic(
|
||||||
reg_addr: tuple[str, int | str],
|
reg_addr: tuple[str, int | str],
|
||||||
forkserver_spawn_method,
|
forkserver_spawn_method,
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
Happy-path: spawn ONE subactor via the
|
Happy-path: spawn ONE subactor via the
|
||||||
`subint_forkserver` backend (parent-side fork from a
|
`main_thread_forkserver` backend (parent-side fork from a
|
||||||
main-interp worker thread), do a trivial portal-RPC
|
main-interp worker thread), do a trivial portal-RPC
|
||||||
round-trip, tear the nursery down cleanly.
|
round-trip, tear the nursery down cleanly.
|
||||||
|
|
||||||
If this passes, the "forkserver + tractor runtime" arch
|
If this passes, the "forkserver + tractor runtime" arch
|
||||||
is proven end-to-end: the registered
|
is proven end-to-end: the registered
|
||||||
`subint_forkserver_proc` spawn target successfully
|
`main_thread_forkserver_proc` spawn target successfully
|
||||||
forks a child, the child runs `_actor_child_main()` +
|
forks a child, the child runs `_actor_child_main()` +
|
||||||
completes IPC handshake + serves an RPC, and the parent
|
completes IPC handshake + serves an RPC, and the parent
|
||||||
reaps via `_ForkedProc.wait()` without regressing any of
|
reaps via `_ForkedProc.wait()` without regressing any of
|
||||||
|
|
@ -326,7 +329,7 @@ def test_subint_forkserver_spawn_basic(
|
||||||
deadline: float = 20.0
|
deadline: float = 20.0
|
||||||
with dump_on_hang(
|
with dump_on_hang(
|
||||||
seconds=deadline,
|
seconds=deadline,
|
||||||
path='/tmp/subint_forkserver_spawn_basic.dump',
|
path='/tmp/main_thread_forkserver_spawn_basic.dump',
|
||||||
):
|
):
|
||||||
trio.run(
|
trio.run(
|
||||||
partial(
|
partial(
|
||||||
|
|
@ -340,7 +343,7 @@ def test_subint_forkserver_spawn_basic(
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# tier-4 DRAFT: orphaned-subactor SIGINT survivability
|
# tier-4 DRAFT: orphaned-subactor SIGINT survivability
|
||||||
#
|
#
|
||||||
# Motivating question: with `subint_forkserver`, the child's
|
# Motivating question: with `main_thread_forkserver`, the child's
|
||||||
# `trio.run()` lives on the fork-inherited worker thread which
|
# `trio.run()` lives on the fork-inherited worker thread which
|
||||||
# is NOT `threading.main_thread()` — so trio cannot install its
|
# is NOT `threading.main_thread()` — so trio cannot install its
|
||||||
# `signal.set_wakeup_fd`-based SIGINT handler. If the parent
|
# `signal.set_wakeup_fd`-based SIGINT handler. If the parent
|
||||||
|
|
@ -360,7 +363,7 @@ def test_subint_forkserver_spawn_basic(
|
||||||
# Cross-backend generalization (decide after this passes):
|
# Cross-backend generalization (decide after this passes):
|
||||||
# - applicable to any backend whose subactors are separate OS
|
# - applicable to any backend whose subactors are separate OS
|
||||||
# processes: `trio`, `mp_spawn`, `mp_forkserver`,
|
# processes: `trio`, `mp_spawn`, `mp_forkserver`,
|
||||||
# `subint_forkserver`.
|
# `main_thread_forkserver`.
|
||||||
# - NOT applicable to plain `subint` (subactors are in-process
|
# - NOT applicable to plain `subint` (subactors are in-process
|
||||||
# subinterpreters, no orphan child process to SIGINT).
|
# subinterpreters, no orphan child process to SIGINT).
|
||||||
# - move path: lift the harness script into
|
# - move path: lift the harness script into
|
||||||
|
|
@ -446,7 +449,7 @@ def _process_alive(pid: int) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# Known-gap test — `subint_forkserver` orphan-SIGINT
|
# Known-gap test — `main_thread_forkserver` orphan-SIGINT
|
||||||
# handling. See
|
# handling. See
|
||||||
# `ai/conc-anal/subint_forkserver_orphan_sigint_hang_issue.md`.
|
# `ai/conc-anal/subint_forkserver_orphan_sigint_hang_issue.md`.
|
||||||
# `strict=True` so if a future fix closes the gap the
|
# `strict=True` so if a future fix closes the gap the
|
||||||
|
|
@ -471,12 +474,12 @@ def test_orphaned_subactor_sigint_cleanup_DRAFT(
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
DRAFT — orphaned-subactor SIGINT survivability under the
|
DRAFT — orphaned-subactor SIGINT survivability under the
|
||||||
`subint_forkserver` backend.
|
`main_thread_forkserver` backend.
|
||||||
|
|
||||||
Sequence:
|
Sequence:
|
||||||
1. Spawn a harness subprocess that brings up a root
|
1. Spawn a harness subprocess that brings up a root
|
||||||
actor + one `sleep_forever` subactor via
|
actor + one `sleep_forever` subactor via
|
||||||
`subint_forkserver`.
|
`main_thread_forkserver`.
|
||||||
2. Read the harness's stdout for `PARENT_READY=<pid>`
|
2. Read the harness's stdout for `PARENT_READY=<pid>`
|
||||||
and `CHILD_PID=<pid>` markers (confirms the
|
and `CHILD_PID=<pid>` markers (confirms the
|
||||||
parent→child IPC handshake completed).
|
parent→child IPC handshake completed).
|
||||||
|
|
@ -524,7 +527,7 @@ def test_orphaned_subactor_sigint_cleanup_DRAFT(
|
||||||
[
|
[
|
||||||
sys.executable,
|
sys.executable,
|
||||||
str(script_path),
|
str(script_path),
|
||||||
'subint_forkserver',
|
'main_thread_forkserver',
|
||||||
host,
|
host,
|
||||||
str(port),
|
str(port),
|
||||||
],
|
],
|
||||||
|
|
@ -577,7 +580,7 @@ def test_orphaned_subactor_sigint_cleanup_DRAFT(
|
||||||
|
|
||||||
pytest.fail(
|
pytest.fail(
|
||||||
f'Orphan subactor (pid={child_pid}) did NOT exit '
|
f'Orphan subactor (pid={child_pid}) did NOT exit '
|
||||||
f'within 10s of SIGINT under `subint_forkserver` '
|
f'within 10s of SIGINT under `main_thread_forkserver` '
|
||||||
f'→ trio on non-main thread did not observe the '
|
f'→ trio on non-main thread did not observe the '
|
||||||
f'default CPython KeyboardInterrupt; backend needs '
|
f'default CPython KeyboardInterrupt; backend needs '
|
||||||
f'explicit SIGINT plumbing.'
|
f'explicit SIGINT plumbing.'
|
||||||
|
|
@ -154,21 +154,6 @@ from trio import TaskStatus
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
from ._subint import _has_subints
|
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 as _ForkedProc,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from tractor.discovery._addr import UnwrappedAddress
|
from tractor.discovery._addr import UnwrappedAddress
|
||||||
from tractor.runtime._portal import Portal
|
from tractor.runtime._portal import Portal
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue