diff --git a/pyproject.toml b/pyproject.toml index ebf044f8..bd46a634 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,11 @@ testing = [ # https://docs.pytest.org/en/8.0.x/explanation/goodpractices.html#choosing-a-test-layout-import-rules "pytest>=8.3.5", "pexpect>=4.9.0,<5", + # per-test wall-clock bound (used via + # `@pytest.mark.timeout(..., method='thread')` on the + # known-hanging `subint`-backend audit tests; see + # `ai/conc-anal/subint_*_issue.md`). + "pytest-timeout>=2.3", ] repl = [ "pyperclip>=1.9.0", diff --git a/tests/discovery/test_registrar.py b/tests/discovery/test_registrar.py index 6f34b117..bd015608 100644 --- a/tests/discovery/test_registrar.py +++ b/tests/discovery/test_registrar.py @@ -517,6 +517,22 @@ async def kill_transport( # @pytest.mark.parametrize('use_signal', [False, True]) +# +# Wall-clock bound via `pytest-timeout` (`method='thread'`). +# Under `--spawn-backend=subint` this test can wedge in an +# un-Ctrl-C-able state (abandoned-subint + shared-GIL +# starvation → signal-wakeup-fd pipe fills → SIGINT silently +# dropped; see `ai/conc-anal/subint_sigint_starvation_issue.md`). +# `method='thread'` is specifically required because `signal`- +# method SIGALRM suffers the same GIL-starvation path and +# wouldn't fire the Python-level handler. +# At timeout the plugin hard-kills the pytest process — that's +# the intended behavior here; the alternative is an unattended +# suite run that never returns. +@pytest.mark.timeout( + 3, # NOTE should be a 2.1s happy path. + method='thread', +) def test_stale_entry_is_deleted( debug_mode: bool, daemon: subprocess.Popen, diff --git a/tests/test_subint_cancellation.py b/tests/test_subint_cancellation.py index 04b6cc9e..18cbf78b 100644 --- a/tests/test_subint_cancellation.py +++ b/tests/test_subint_cancellation.py @@ -161,6 +161,20 @@ def test_subint_happy_teardown( trio.run(partial(_happy_path, reg_addr, deadline)) +# Wall-clock bound via `pytest-timeout` (`method='thread'`) +# as defense-in-depth over the inner `trio.fail_after(15)`. +# Under the orphaned-channel hang class described in +# `ai/conc-anal/subint_cancel_delivery_hang_issue.md`, SIGINT +# is still deliverable and this test *should* be unwedgeable +# by the inner trio timeout — but sibling subint-backend +# tests in this repo have also exhibited the +# `subint_sigint_starvation_issue.md` GIL-starvation flavor, +# so `method='thread'` keeps us safe in case ordering or +# load shifts the failure mode. +@pytest.mark.timeout( + 3, # NOTE never passes pre-3.14+ subints support. + method='thread', +) def test_subint_non_checkpointing_child( reg_addr: tuple[str, int|str], ) -> None: