diff --git a/tests/discovery/test_registrar.py b/tests/discovery/test_registrar.py index c1759d5e..7a17ac46 100644 --- a/tests/discovery/test_registrar.py +++ b/tests/discovery/test_registrar.py @@ -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' diff --git a/tests/test_cancellation.py b/tests/test_cancellation.py index fe41dc99..28168fd6 100644 --- a/tests/test_cancellation.py +++ b/tests/test_cancellation.py @@ -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', diff --git a/tests/test_infected_asyncio.py b/tests/test_infected_asyncio.py index 6535265f..8157e6d4 100644 --- a/tests/test_infected_asyncio.py +++ b/tests/test_infected_asyncio.py @@ -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=( diff --git a/tests/test_shm.py b/tests/test_shm.py index 8ea43457..84d0988e 100644 --- a/tests/test_shm.py +++ b/tests/test_shm.py @@ -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' ) ) diff --git a/tests/test_spawning.py b/tests/test_spawning.py index b0e8a88d..63a2fb8e 100644 --- a/tests/test_spawning.py +++ b/tests/test_spawning.py @@ -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-" diff --git a/tractor/_root.py b/tractor/_root.py index d76c15ff..78a1be56 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -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', ) diff --git a/tractor/runtime/_runtime.py b/tractor/runtime/_runtime.py index 1cb947f5..771eb49c 100644 --- a/tractor/runtime/_runtime.py +++ b/tractor/runtime/_runtime.py @@ -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( diff --git a/tractor/runtime/_state.py b/tractor/runtime/_state.py index 490c09fc..11e0c0fd 100644 --- a/tractor/runtime/_state.py +++ b/tractor/runtime/_state.py @@ -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))