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
parent
bb239e847f
commit
1cafaecf52
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue