Use trace CM helpers in `test_infected_asyncio`

Adopt the `_testing.trace` CM helpers in two MTF-hang-prone
tests so on-timeout we get a fresh
`ptree`/`wchan`/`py-spy` diag snapshot on disk instead of
opaque pytest timeout-kills. Same shape as bd07a95d for
`test_dynamic_pub_sub`.

Deats,
- `test_echoserver_detailed_mechanics`:
  * inner `trio.fail_after` → `fail_after_w_trace`. Adds
    `fail_after_w_trace: FailAfterWTraceFactory` fixture
    param.
  * mv per-backend `timeout` calc to top of test body (was
    interleaved w/ helper defs).
  * factor deep
    `open_nursery`/`open_context`/`open_stream` body into
    `_body()` so the wrapping `main()` stays a 2-liner —
    keeps the nested-CM block at its natural indent level
    instead of pushing it under yet another `async with`.
  * drop `with_timeout: bool` knob + `fa_main()` helper
    (knob was hard-coded `True`).
- `test_sigint_closes_lifetime_stack`:
  * outer `signal.alarm`/`try`/`finally` → single
    `afk_alarm_w_trace(10)` CM. Adds
    `afk_alarm_w_trace: AfkAlarmWTraceFactory` fixture
    param.
  * drop `_AFK_CAP_S` + `armed_alarm` vars (CM owns both).
  * explanatory comment refreshed to mention
    `AFKAlarmTimeout` + the disk-snapshot side effect.

Other,
- Drop debug `return 1e3` short-circuit from `delay()`
  fixture — snuck in as a scratch line, was clobbering the
  proper `debug_mode`-branched return.
- Top-level import: `FailAfterWTraceFactory`,
  `AfkAlarmWTraceFactory` from `tractor._testing.trace`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
trionics.start_or_cancel
Gud Boi 2026-05-18 15:22:26 -04:00
parent bb239e847f
commit 1cafaecf52
1 changed files with 49 additions and 47 deletions

View File

@ -30,6 +30,10 @@ from tractor import (
from tractor.runtime import _state from tractor.runtime import _state
from tractor.trionics import BroadcastReceiver from tractor.trionics import BroadcastReceiver
from tractor._testing import expect_ctxc from tractor._testing import expect_ctxc
from tractor._testing.trace import (
AfkAlarmWTraceFactory,
FailAfterWTraceFactory,
)
# Per-test zombie-subactor reaper. Opt-in (NOT autouse) — # Per-test zombie-subactor reaper. Opt-in (NOT autouse) —
@ -58,7 +62,6 @@ pytestmark = pytest.mark.usefixtures(
scope='module', scope='module',
) )
def delay(debug_mode: bool) -> int: def delay(debug_mode: bool) -> int:
return 1e3
if debug_mode: if debug_mode:
return 999 return 999
else: else:
@ -846,8 +849,25 @@ def test_echoserver_detailed_mechanics(
raise_error_mid_stream, raise_error_mid_stream,
is_forking_spawner: bool, is_forking_spawner: bool,
fail_after_w_trace: FailAfterWTraceFactory,
): ):
async def main(): # NOTE: under fork-based backends the cancel-cascade
# path is structurally slower than `trio`'s subproc-exec
# (per-spawn forkserver-handshake compounds during
# teardown). Bump the cap so cross-test contamination
# doesn't flake this — see
# `ai/conc-anal/cancel_cascade_too_slow_under_main_thread_forkserver_issue.md`.
timeout: float = (
999 if tractor.debug_mode()
else 4 if is_forking_spawner
else 1
)
# body factored out so the `fail_after_w_trace`-wrapping
# `main()` stays a 2-liner — keeps the deep `open_nursery`
# /`open_context`/`open_stream` block at its natural indent
# level instead of pushing it under yet another `async with`.
async def _body():
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
debug_mode=debug_mode, debug_mode=debug_mode,
@ -893,34 +913,21 @@ def test_echoserver_detailed_mechanics(
# is cancelled by kbi or out of task cancellation # is cancelled by kbi or out of task cancellation
await p.cancel_actor() await p.cancel_actor()
# NOTE: under fork-based backends the cancel-cascade async def main():
# path is structurally slower than `trio`'s subproc-exec # on-timeout diag snapshot via `fail_after_w_trace`
# (per-spawn forkserver-handshake compounds during # — when the cancel cascade hangs under MTF we get a
# teardown). Bump the cap so cross-test contamination # fresh `ptree`/`wchan`/`py-spy` dump on disk INSTEAD
# doesn't flake this — see # of an opaque pytest timeout-kill. See
# `ai/conc-anal/cancel_cascade_too_slow_under_main_thread_forkserver_issue.md`. # `tractor/_testing/trace.py`.
timeout: float = ( async with fail_after_w_trace(timeout):
999 if tractor.debug_mode() await _body()
else 4 if is_forking_spawner
else 1
)
with_timeout: bool = (
True
# False
)
async def fa_main():
if with_timeout:
with trio.fail_after(timeout):
await main()
else:
await main()
if raise_error_mid_stream: if raise_error_mid_stream:
with pytest.raises(raise_error_mid_stream): with pytest.raises(raise_error_mid_stream):
trio.run(fa_main) trio.run(main)
else: else:
trio.run(fa_main) trio.run(main)
@tractor.context @tractor.context
@ -1074,6 +1081,7 @@ def test_sigint_closes_lifetime_stack(
trio_side_is_shielded: bool, trio_side_is_shielded: bool,
send_sigint_to: str, send_sigint_to: str,
is_forking_spawner: bool, is_forking_spawner: bool,
afk_alarm_w_trace: AfkAlarmWTraceFactory,
): ):
''' '''
Ensure that an infected child can use the `Actor.lifetime_stack` Ensure that an infected child can use the `Actor.lifetime_stack`
@ -1221,32 +1229,26 @@ def test_sigint_closes_lifetime_stack(
assert not tmp_file.exists() assert not tmp_file.exists()
assert ctx.maybe_error assert ctx.maybe_error
# outer signal-based AFK-safety guard. mirrors the pattern in # outer hard wall-clock backstop via `afk_alarm_w_trace`:
# `tests/test_advanced_streaming.py::test_dynamic_pub_sub`: when # when the in-band trio cancel path doesn't fire (e.g.
# the in-band trio cancel path doesn't fire (e.g. parent is # parent is parked in a shielded `await` inside actor-
# parked in a shielded `await` inside actor-nursery teardown, or # nursery teardown, or `open_context.__aenter__` hangs
# `open_context.__aenter__` hangs waiting for a child's # waiting for a child's `StartAck` that never comes), the
# `StartAck` that never comes), `signal.alarm` raises KBI in the # `signal.alarm` inside the CM raises `AFKAlarmTimeout`
# main thread regardless of trio's scope state. This caps the # in the main thread regardless of trio's scope state —
# absolute wall-clock so an AFK run can't sit for an hour on a # AND captures a full diag snapshot to
# forkserver-launchpad-contamination hang. Only armed under fork- # `$XDG_CACHE_HOME/tractor/hung-dumps/` before re-raising.
# based backends since the bug class is MTF-specific. # Only armed under fork-based backends since this hang-
_AFK_CAP_S: int = ( # class is MTF-specific.
999 if debug_mode if (
else 10
)
armed_alarm: bool = (
not debug_mode not debug_mode
and and
is_forking_spawner is_forking_spawner
) ):
if armed_alarm: with afk_alarm_w_trace(10):
signal.alarm(_AFK_CAP_S) trio.run(main)
try: else:
trio.run(main) trio.run(main)
finally:
if armed_alarm:
signal.alarm(0)