Sweep `subint_forkserver` → `main_thread_forkserver` in code

After the variant-1 / variant-2 backend split, update remaining
string-match refs to the variant-1 backend so user-visible gates
+ skip-marks + comments name the working backend correctly:

- `tractor._root._DEBUG_COMPATIBLE_BACKENDS`: include
  `main_thread_forkserver`, drop the stub-only `subint_forkserver`
  entry.
- `tests/test_spawning.py::test_loglevel_propagated_to_subactor`:
  capfd-skip flips to `main_thread_forkserver`.
- `tests/test_infected_asyncio.py::test_sigint_closes_lifetime_stack`:
  xfail-condition flips to `main_thread_forkserver`.
- `tests/test_shm.py`: drop stale "broken on `main_thread_forkserver`"
  reason-text since the `mp.SharedMemory(track=False)`
  + resource-tracker monkey-patch in `.ipc._mp_bs` makes the tests pass;
  the skip-mark only fires on plain `subint` now.
- Comment / docstring sweep: `runtime._state`, `runtime._runtime`,
  `_testing.pytest`, `_subint.py`, `pyproject.toml`,
  `test_cancellation.py`, `test_registrar.py` — refs to variant-1
  backend updated.

(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 205382a39b)
(factored: dropped spawn-backend-only path: tractor/spawn/_subint.py)
test_suite_hardening
Gud Boi 2026-06-09 20:23:12 -04:00
parent 00d050fa01
commit c69b0e82f0
8 changed files with 24 additions and 35 deletions

View File

@ -538,13 +538,13 @@ async def kill_transport(
@pytest.mark.timeout( @pytest.mark.timeout(
30, 30,
# NOTE should be a 2.1s happy path. # NOTE should be a 2.1s happy path.
# XXX for `subint_forkserver` this is SUPER SENSITIVE so keep it # XXX for `main_thread_forkserver` this is SUPER SENSITIVE
# higher to avoid flaky runs.. # so keep it higher to avoid flaky runs..
method='thread', method='thread',
) )
@pytest.mark.skipon_spawn_backend( @pytest.mark.skipon_spawn_backend(
'subint', 'subint',
# 'subint_forkserver', # 'main_thread_forkserver',
reason=( reason=(
'XXX SUBINT HANGING TEST XXX\n' 'XXX SUBINT HANGING TEST XXX\n'
'See oustanding issue(s)\n' 'See oustanding issue(s)\n'

View File

@ -452,21 +452,12 @@ async def spawn_and_error(
await nursery.run_in_actor(*args, **kwargs) await nursery.run_in_actor(*args, **kwargs)
@pytest.mark.skipon_spawn_backend( # NOTE: `main_thread_forkserver` capture-fd hang class is no
'subint_forkserver', # longer skipped here — `--capture=sys` (the new `pyproject.toml`
reason=( # default) sidesteps the pipe-buffer-fill deadlock for
'Passes cleanly with `pytest -s` (no stdout capture) ' # `test_nested_multierrors`. See
'but hangs under default `--capture=fd` due to ' # `ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md`
'pytest-capture-pipe buffer fill from high-volume ' # / #449 for the post-mortem.
'subactor error-log traceback output inherited via fds '
'1,2 in fork children. Fix direction: redirect subactor '
'stdout/stderr to `/dev/null` in `_child_target` / '
'`_actor_child_main` so forkserver children don\'t hold '
'pytest\'s capture pipe open. See `ai/conc-anal/'
'subint_forkserver_test_cancellation_leak_issue.md` '
'"Update — pytest capture pipe is the final gate".'
),
)
@pytest.mark.timeout( @pytest.mark.timeout(
10, 10,
method='thread', method='thread',

View File

@ -1129,7 +1129,7 @@ def test_sigint_closes_lifetime_stack(
if ( if (
send_sigint_to == 'child' send_sigint_to == 'child'
and and
start_method == 'subint_forkserver' start_method == 'main_thread_forkserver'
): ):
pytest.xfail( pytest.xfail(
reason=( reason=(

View File

@ -16,18 +16,16 @@ from tractor.ipc._shm import (
pytestmark = pytest.mark.skipon_spawn_backend( pytestmark = pytest.mark.skipon_spawn_backend(
'subint', 'subint',
# 'subint_forkserver', # NOTE, `main_thread_forkserver` works for these tests
# via the `mp.SharedMemory(track=False)` +
# `mp.resource_tracker` monkey-patch in `.ipc._mp_bs`.
# Without that workaround the fork-inherited
# `resource_tracker` fd would EBADF on first shm op +
# cascade into `FileExistsError` across parametrize
# variants. Tracker doc:
# `ai/conc-anal/subint_forkserver_mp_shared_memory_issue.md`.
reason=( reason=(
'subint: GIL-contention hanging class.\n' 'subint: GIL-contention hanging class.\n'
'subint_forkserver: `multiprocessing.SharedMemory` '
'is fork-without-exec unsafe — child inherits parent\'s '
'`resource_tracker` fd → EBADF on first shm op '
'(`test_child_attaches_alot`); leaked `/shm_list` from '
'a "passing" run cascades into `FileExistsError` across '
'parametrize variants (`test_parent_writer_child_reader`). '
'Canonical CPython issue class, NOT a tractor bug; full '
'tracker doc:\n'
'ai/conc-anal/subint_forkserver_mp_shared_memory_issue.md'
) )
) )

View File

@ -194,7 +194,7 @@ def test_loglevel_propagated_to_subactor(
reg_addr: tuple, reg_addr: tuple,
level: str, level: str,
): ):
if start_method in ('mp_forkserver', 'subint_forkserver'): if start_method in ('mp_forkserver', 'main_thread_forkserver'):
pytest.skip( pytest.skip(
"a bug with `capfd` seems to make forkserver capture not work? " "a bug with `capfd` seems to make forkserver capture not work? "
"(same class as the `mp_forkserver` pre-existing skip — fork-" "(same class as the `mp_forkserver` pre-existing skip — fork-"

View File

@ -79,7 +79,7 @@ _DEBUG_COMPATIBLE_BACKENDS: tuple[str, ...] = (
'trio', 'trio',
# forkserver children run `_trio_main` in their own OS # forkserver children run `_trio_main` in their own OS
# process — same child-side runtime shape as `trio_proc`. # process — same child-side runtime shape as `trio_proc`.
'subint_forkserver', 'main_thread_forkserver',
) )

View File

@ -1778,7 +1778,7 @@ async def async_main(
# shielded loop would park on the parent chan # shielded loop would park on the parent chan
# indefinitely waiting for EOF that only arrives # indefinitely waiting for EOF that only arrives
# after the PARENT tears down, which under # after the PARENT tears down, which under
# fork-based backends (e.g. `subint_forkserver`) # fork-based backends (e.g. `main_thread_forkserver`)
# it waits on THIS actor's exit — deadlock. # it waits on THIS actor's exit — deadlock.
actor._parent_chan_cs = await root_tn.start( actor._parent_chan_cs = await root_tn.start(
partial( partial(

View File

@ -123,8 +123,8 @@ class RuntimeVars(Struct):
# `open_root_actor()` nor received a parent `SpawnSpec`. Kept # `open_root_actor()` nor received a parent `SpawnSpec`. Kept
# as a module-level constant so `get_runtime_vars(clear_values= # as a module-level constant so `get_runtime_vars(clear_values=
# True)` can reset the live dict back to this baseline (see # True)` can reset the live dict back to this baseline (see
# `tractor.spawn._subint_forkserver` for the one current caller # `tractor.spawn._main_thread_forkserver` for the one current
# that needs it). # caller that needs it).
_RUNTIME_VARS_DEFAULTS: dict[str, Any] = { _RUNTIME_VARS_DEFAULTS: dict[str, Any] = {
# root of actor-process tree info # root of actor-process tree info
'_is_root': False, # bool '_is_root': False, # bool
@ -167,7 +167,7 @@ def get_runtime_vars(
defaults (`_RUNTIME_VARS_DEFAULTS`) instead of the live defaults (`_RUNTIME_VARS_DEFAULTS`) instead of the live
dict. Useful in combination with `set_runtime_vars()` to dict. Useful in combination with `set_runtime_vars()` to
reset process-global state back to "cold" the main caller reset process-global state back to "cold" the main caller
today is the `subint_forkserver` spawn backend's post-fork today is the `main_thread_forkserver` spawn backend's post-fork
child prelude: child prelude:
set_runtime_vars(get_runtime_vars(clear_values=True)) set_runtime_vars(get_runtime_vars(clear_values=True))