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
parent
00d050fa01
commit
c69b0e82f0
|
|
@ -538,13 +538,13 @@ async def kill_transport(
|
|||
@pytest.mark.timeout(
|
||||
30,
|
||||
# NOTE should be a 2.1s happy path.
|
||||
# XXX for `subint_forkserver` this is SUPER SENSITIVE so keep it
|
||||
# higher to avoid flaky runs..
|
||||
# XXX for `main_thread_forkserver` this is SUPER SENSITIVE
|
||||
# so keep it higher to avoid flaky runs..
|
||||
method='thread',
|
||||
)
|
||||
@pytest.mark.skipon_spawn_backend(
|
||||
'subint',
|
||||
# 'subint_forkserver',
|
||||
# 'main_thread_forkserver',
|
||||
reason=(
|
||||
'XXX SUBINT HANGING TEST XXX\n'
|
||||
'See oustanding issue(s)\n'
|
||||
|
|
|
|||
|
|
@ -452,21 +452,12 @@ async def spawn_and_error(
|
|||
await nursery.run_in_actor(*args, **kwargs)
|
||||
|
||||
|
||||
@pytest.mark.skipon_spawn_backend(
|
||||
'subint_forkserver',
|
||||
reason=(
|
||||
'Passes cleanly with `pytest -s` (no stdout capture) '
|
||||
'but hangs under default `--capture=fd` due to '
|
||||
'pytest-capture-pipe buffer fill from high-volume '
|
||||
'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".'
|
||||
),
|
||||
)
|
||||
# NOTE: `main_thread_forkserver` capture-fd hang class is no
|
||||
# longer skipped here — `--capture=sys` (the new `pyproject.toml`
|
||||
# default) sidesteps the pipe-buffer-fill deadlock for
|
||||
# `test_nested_multierrors`. See
|
||||
# `ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md`
|
||||
# / #449 for the post-mortem.
|
||||
@pytest.mark.timeout(
|
||||
10,
|
||||
method='thread',
|
||||
|
|
|
|||
|
|
@ -1129,7 +1129,7 @@ def test_sigint_closes_lifetime_stack(
|
|||
if (
|
||||
send_sigint_to == 'child'
|
||||
and
|
||||
start_method == 'subint_forkserver'
|
||||
start_method == 'main_thread_forkserver'
|
||||
):
|
||||
pytest.xfail(
|
||||
reason=(
|
||||
|
|
|
|||
|
|
@ -16,18 +16,16 @@ from tractor.ipc._shm import (
|
|||
|
||||
pytestmark = pytest.mark.skipon_spawn_backend(
|
||||
'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=(
|
||||
'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'
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ def test_loglevel_propagated_to_subactor(
|
|||
reg_addr: tuple,
|
||||
level: str,
|
||||
):
|
||||
if start_method in ('mp_forkserver', 'subint_forkserver'):
|
||||
if start_method in ('mp_forkserver', 'main_thread_forkserver'):
|
||||
pytest.skip(
|
||||
"a bug with `capfd` seems to make forkserver capture not work? "
|
||||
"(same class as the `mp_forkserver` pre-existing skip — fork-"
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ _DEBUG_COMPATIBLE_BACKENDS: tuple[str, ...] = (
|
|||
'trio',
|
||||
# forkserver children run `_trio_main` in their own OS
|
||||
# process — same child-side runtime shape as `trio_proc`.
|
||||
'subint_forkserver',
|
||||
'main_thread_forkserver',
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1778,7 +1778,7 @@ async def async_main(
|
|||
# shielded loop would park on the parent chan
|
||||
# indefinitely waiting for EOF that only arrives
|
||||
# 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.
|
||||
actor._parent_chan_cs = await root_tn.start(
|
||||
partial(
|
||||
|
|
|
|||
|
|
@ -123,8 +123,8 @@ class RuntimeVars(Struct):
|
|||
# `open_root_actor()` nor received a parent `SpawnSpec`. Kept
|
||||
# as a module-level constant so `get_runtime_vars(clear_values=
|
||||
# True)` can reset the live dict back to this baseline (see
|
||||
# `tractor.spawn._subint_forkserver` for the one current caller
|
||||
# that needs it).
|
||||
# `tractor.spawn._main_thread_forkserver` for the one current
|
||||
# caller that needs it).
|
||||
_RUNTIME_VARS_DEFAULTS: dict[str, Any] = {
|
||||
# root of actor-process tree info
|
||||
'_is_root': False, # bool
|
||||
|
|
@ -167,7 +167,7 @@ def get_runtime_vars(
|
|||
defaults (`_RUNTIME_VARS_DEFAULTS`) instead of the live
|
||||
dict. Useful in combination with `set_runtime_vars()` to
|
||||
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:
|
||||
|
||||
set_runtime_vars(get_runtime_vars(clear_values=True))
|
||||
|
|
|
|||
Loading…
Reference in New Issue