tractor/tests
Gud Boi 0ef549fadb Add `tractor.trionics.patches` subpkg + first fix
With a seminal patch fixing `trio`'s `WakeupSocketpair.drain()` which
can busy-loop due to lack of handling `EOF`.

New `tractor.trionics.patches` subpkg housing defensive monkey-patches
for upstream `trio` bugs we've encountered while running `tractor`
— particularly as of recent, fork-survival edge cases that haven't been
filed/fixed upstream yet. Each patch is idempotent, version-gated via
`is_needed()`, and carries a `# REMOVE WHEN:` marker pointing at the
upstream release whose adoption allows deletion.

Subpkg layout + per-patch contract documented in
`tractor/trionics/patches/README.md` — `apply()` / `is_needed()`
/ `repro()` API, registry pattern via `_PATCHES` in `__init__.py`,
single-call entry point `apply_all()`.

First patch, `_wakeup_socketpair`:
- `trio`'s `WakeupSocketpair.drain()` loops on `recv(64KB)` and exits
  ONLY on `BlockingIOError`, NEVER on `recv() == b''` (peer-closed FIN).
- under `fork()`-spawning backends the COW-inherited socketpair fds
  & `_close_inherited_fds()` teardown can leave a `WakeupSocketpair`
  instance whose write-end is closed, and `drain()` then **spins forever
  in C with no Python checkpoints**,
- this obviously burns 100% CPU and no signal delivery.

Standalone repro:

    from trio._core._wakeup_socketpair import WakeupSocketpair
    ws = WakeupSocketpair()
    ws.write_sock.close()
    ws.drain()  # spins forever

Patch is one-line — break the drain loop on b'' EOF.

Manifested as two distinct test failures:

- `tests/test_multi_program.py::test_register_duplicate_name` hung at
  100% CPU on the busy-loop directly (fork child's worker thread)
- `tests/test_infected_asyncio.py::test_aio_simple_error` Mode-A
  deadlock — busy-loop wedged trio's scheduler inside `start_guest_run`,
  both threads parked in `epoll_wait`, no TCP connect-back to parent
  ever happened.

Same patch fixes both. Restored 99.7% pass rate on full
suite under `--spawn-backend=main_thread_forkserver`
(was hanging indefinitely before).

Wired into `tractor._child._actor_child_main` via `apply_all()` BEFORE
any trio runtime init. Harmless on non-fork backends.

Conc-anal write-ups, including strace + py-spy evidence:

- `ai/conc-anal/trio_wakeup_socketpair_busy_loop_under_fork_issue.md`
- `ai/conc-anal/infected_asyncio_under_main_thread_forkserver_hang_issue.md`

Regression tests in `tests/trionics/test_patches.py`: each test asserts
(a) the bug exists pre-patch (or is fixed upstream — skip cleanly), (b)
the patch fixes it with a SIGALRM wall-clock cap so a regression hangs
loud instead of silently.

TODO:
- [ ] file the upstream `python-trio/trio` issue + PR.
- [ ] use the `repro()` callable in `_wakeup_socketpair.py` IS the issue
      body's evidence section.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-05-04 12:18:03 -04:00
..
devx Adjust `test_shield_pause` for capsys backends 2026-05-01 19:08:55 -04:00
discovery Drop test-local timeouts, +`sync_pause` to dev 2026-04-29 18:10:40 -04:00
ipc Update tests+examples imports for new subpkgs 2026-04-02 17:59:13 -04:00
msg Backend-aware timeout in `maybe_expect_raises` 2026-04-29 10:21:56 -04:00
spawn Drop test-local timeouts, +`sync_pause` to dev 2026-04-29 18:10:40 -04:00
trionics Add `tractor.trionics.patches` subpkg + first fix 2026-05-04 12:18:03 -04:00
__init__.py Add `tests/__init__.py` for `.conftest` imports 2025-03-20 20:53:54 -04:00
conftest.py Default `--ll` to `None` in test harness 2026-05-01 00:18:18 -04:00
test_2way.py Tidy a typing-typo, add explicit `ids=` for paramed suites 2026-03-09 19:35:47 -04:00
test_advanced_faults.py Revert advanced-fault UDS edge case handling 2026-03-13 21:10:52 -04:00
test_advanced_streaming.py Drop test-local timeouts, +`sync_pause` to dev 2026-04-29 18:10:40 -04:00
test_cancellation.py Drop test-local timeouts, +`sync_pause` to dev 2026-04-29 18:10:40 -04:00
test_child_manages_service_nursery.py Swap `open_channel_from()` to yield `(chan, first)` 2026-03-13 19:28:57 -04:00
test_clustering.py Skip `test_empty_mngrs_input_raises` on UDS tpt 2026-04-02 17:59:13 -04:00
test_context_stream_semantics.py Wire `reg_addr` into `test_context_stream_semantics` 2026-04-27 13:52:28 -04:00
test_docs_examples.py Move `get_cpu_state()` to `conftest` as shared latency headroom 2026-04-02 17:59:13 -04:00
test_infected_asyncio.py Add opt-in `reap_subactors_per_test` fixture 2026-04-27 21:41:02 -04:00
test_inter_peer_cancellation.py Mark `subint`-hanging tests with `skipon_spawn_backend` 2026-04-23 18:47:49 -04:00
test_legacy_one_way_streaming.py Move `get_cpu_state()` to `conftest` as shared latency headroom 2026-04-02 17:59:13 -04:00
test_local.py Rename `Arbiter` -> `Registrar`, mv to `discovery._registry` 2026-04-02 17:59:13 -04:00
test_log_sys.py Mk `test_implicit_mod_name_applied_for_child()` check init-mods 2026-02-11 21:43:37 -05:00
test_multi_program.py Rename `discovery._discovery` to `._api` 2026-04-14 19:54:14 -04:00
test_oob_cancellation.py Woops, fix missing `assert` thanks to copilot 2025-09-11 13:13:18 -04:00
test_pubsub.py Mark `subint`-hanging tests with `skipon_spawn_backend` 2026-04-23 18:47:49 -04:00
test_reg_err_types.py Drop stale `.cancel()`, fix docstring typo in tests 2026-04-02 18:21:19 -04:00
test_remote_exc_relay.py Adjust ep-masking-suite for the real-use-case 2025-07-15 07:23:21 -04:00
test_resource_cache.py Scale `test_open_local_sub_to_stream` timeout by CPU factor 2026-04-16 20:03:32 -04:00
test_ringbuf.py Avoid skip `.ipc._ringbuf` import when no `cffi` 2026-04-23 18:47:49 -04:00
test_root_infect_asyncio.py Swap `open_channel_from()` to yield `(chan, first)` 2026-03-13 19:28:57 -04:00
test_root_runtime.py Update tests+examples imports for new subpkgs 2026-04-02 17:59:13 -04:00
test_rpc.py Rename `Arbiter` -> `Registrar`, mv to `discovery._registry` 2026-04-02 17:59:13 -04:00
test_runtime.py Repair lifetime-stack suite's flakiness 2026-03-13 21:10:52 -04:00
test_shm.py Sweep `subint_forkserver` → `main_thread_forkserver` in code 2026-04-27 19:55:37 -04:00
test_spawning.py Sweep `subint_forkserver` → `main_thread_forkserver` in code 2026-04-27 19:55:37 -04:00
test_task_broadcasting.py Tweak timeouts and rm `arbiter_addr` in tests 2026-04-14 19:54:14 -04:00
test_trioisms.py Tweaks from copilot, type fix, typos, language. 2025-09-11 10:01:25 -04:00