Commit Graph

813 Commits (97aff520509834696b61506e42115e02141e815f)

Author SHA1 Message Date
Gud Boi 57febf045d Harden debugger teardown assertions
before hard-reap can print its T-800 marker. Pexpect also replaces
`child.before` at every prompt, hiding earlier nested tracebacks from
the final assertion.

Deats,
- assert cancel-timeout escalation through `proc.kill()`
- prove context-break teardown with EOF and a dead child process
- accumulate nested debugger output across every prompt boundary

Prompt-IO: ai/prompt-io/opencode/20260819T234823Z_557065d8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi 20e89334c0 Reject misplaced empty `runtime_kwargs`
Treat `runtime_kwargs` as provided whenever it is not `None`.
Previously an empty dict bypassed placement validation and was
silently ignored when `an` or `portal` selected an existing runtime.

Reject both placement modes before actor startup for empty and
configured runtime kwargs while preserving empty-dict use when
`to_actor.run()` owns its private runtime.

Caught-during: review remediation
Found-via: `/code-review` P3 option-validation finding

Review: PR #481 (opencode)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-4956692120

Prompt-IO: ai/prompt-io/opencode/20260819T020757Z_b38efed7_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi fe0a724d10 Use linked contexts in `to_actor.run()`
Pass target inputs positionally and normalize every retained
`functools.partial()` layer, including Python 3.14 Placeholder
binding. Validate the complete target signature before startup.

Route each ordinary async fn through a static `@context` endpoint
so remote results, errors and caller cancellation remain linked.
Send namespace and function components separately, then resolve
through `Actor._get_rpc_func()` so the RPC module allowlist remains
authoritative. Retain client-created `NamespacePath` refs so
`to_tuple()` does not re-import their callable.

Owned actors enable the endpoint's `__name__` directly. Keep
`to_actor.MODULE` as the importer-facing alias used by caller-owned
portals while retaining the target module's authorization boundary.

Cover all placement modes, nested partials, argument collisions,
linked cancellation, remote errors and authorization failures.

Caught-during: review remediation
Found-via: `/run-tests` portal cancellation regression

Review: PR #481 (opencode)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-4956692120

Prompt-IO: ai/prompt-io/opencode/20260818T193005Z_bf06b4f8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi 99be161ec0 Clean failed remote-task startup state
`Actor.start_remote_task()` registers its caller context before
sending `Start`, but only cancellation cleaned that state.
Encoding, ack timeout, malformed ack and remote authorization
errors leaked it.

Protect the complete send, acknowledgement and validation phase.
Track successful publication, make a remote cancellation attempt
only when protocol-safe and always release the local context while
preserving the original startup error.

Cover both pre-publication serialization failure and a remote
`ModuleNotExposed` rejection without damaging a reused portal.

Caught-during: review remediation
Found-via: `/run-tests` startup-failure regressions

Review: PR #481 (opencode)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-4956692120

Prompt-IO: ai/prompt-io/opencode/20260818T193004Z_bf06b4f8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi c294a812c3 Bound cancelled remote-task startup
Cancellation after `Start` publication but before `StartAck` can
strand the caller context and leave its remote task running.

Make one shielded, bounded task-cancel request before dropping
local startup state. Keep the private `cancel_on_startup` policy
outside public target kwargs and disable it for the `_cancel_task`
RPC itself so cleanup can not recursively cancel its own startup.

Release each private helper context on exit and prove the
caller-owned actor remains reusable after controlled startup
cancellation.

Caught-during: review remediation
Found-via: `/run-tests` test_cancel_during_context_startup

Review: PR #481 (opencode)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-4956692120

Prompt-IO: ai/prompt-io/opencode/20260818T193003Z_bf06b4f8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi ecf89bfaac Close interrupted `MsgTransport.send()` streams
`SendStream.send_all()` can raise `trio.Cancelled` after writing an
arbitrary prefix of the four-byte length header and payload. The
peer can no longer distinguish a following msg boundary.

Close the stream under a shield before propagating cancellation so
callers can not append another msg to an indeterminate byte stream.

Caught-during: review remediation
Found-via: prospective P2 cancellation review

Review: PR #481 (opencode)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-4956692120

Prompt-IO: ai/prompt-io/opencode/20260818T193001Z_bf06b4f8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi 49213d170e Reap `to_actor.run()` children before return
Give each `ActorNursery` child its own reap request and
completion event. Owned one-shots now wait for process joining and
bookkeeping removal before returning.

Escalate unacknowledged cancellation with `proc.kill()` after an
active debugger releases. Latch nursery-wide teardown for monitors
that finish startup late, and snapshot children before cancellation
checkpoints permit concurrent removal.

Cover immediate managed-nursery cleanup, failed cancel
acknowledgements and late monitor registration across Trio TCP/UDS
and `mp_spawn`.

Caught-during: review remediation
Found-via: `/run-tests` test_late_child_reap_registration_is_released

Review: PR #481 (copilot-pull-request-reviewer)
https://github.com/goodboy/tractor/pull/481#discussion_r3514759131

Prompt-IO: ai/prompt-io/opencode/20260818T031532Z_4151b956_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-21 15:03:34 -04:00
Gud Boi 3aaa3bccbd Add `tests/test_to_actor.py` one-shot API suite
Cover every placement variant + failure mode of the new
`to_actor.run()`,

- private-nursery one-shot + implicit runtime boot via pass-through
  `runtime_kwargs`,
- remote-error relay to the caller's task (bare and inside a
  caller-managed `an`) as boxed `RemoteActorError`s,
- caller-nursery spawn + portal-reuse w/o implicit reap,
- the concurrent "worker-pool-ish" pattern: a local `trio` task
  nursery scheduling one-shots against a shared `an`,
- the 4 pre-spawn validation rejections (sync fn, async-gen fn,
  `portal`+`an` combo, `runtime_kwargs`+placement combo).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-21 15:03:34 -04:00
Gud Boi 33d74da8c2 Fix send-side `MsgTypeError` rendering
Once `pformat_caller_frame()` renders successfully, the default
`_mk_send_mte()` path still fails while formatting the valid IPC
msg spec and then constructs its error message as a one-element
tuple.

Pass `MsgCodec` to `pformat_msgspec()`, keep the assembled message
a string and exercise the complete path through a printable
`MsgTypeError` regression.

Prompt-IO: ai/prompt-io/opencode/20260820T150250Z_9afda1c6_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-20 11:57:39 -04:00
Gud Boi a3d65a6b3d Add a `pformat_caller_frame()` render guard test
`pformat_boxed_tb()` has never accepted an `indent` kwarg but
`pformat_caller_frame(box_tb=True)` has been passing one since
`888af602`. Nothing in the suite covered the branch, so the
`TypeError` only ever surfaced from `_mk_send_mte()` — i.e.
EVERY send-side `MsgTypeError` blew up while formatting itself
and masked the real msg-spec violation behind a bogus
`TypeError`.

Red on purpose per the test-first convention; the 1-line fix
lands next.

Also pin `pformat_boxed_tb()`s signature so a future typo'd
kwarg fails loudly at the call site instead of only when some
rare error path runs.

(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-18 20:12:59 -04:00
Gud Boi ca4582b003 Skip Windows-hanging shm IPC test
`test_parent_writer_child_reader` deadlocks on Windows (the
parent/child shared-mem transfer hangs at the larger frame size),
so the `windows-latest` CI leg ran to the 16-min job cap instead
of completing. It's a genuine nascent-Windows shm bug, not a
clean "unsupported", so it's `skipif`'d (not removed) and tracked
under #404; `test_child_attaches_alot` still runs on Windows.

- `@pytest.mark.skipif(platform.system() == 'Windows', ...)` on
  the parametrized `test_parent_writer_child_reader` so the leg
  completes + reports. linux/macOS unaffected (all 6 variants
  still run).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-14 23:17:11 -04:00
Gud Boi 34d81adef3 Skip `infect_asyncio` tests on Windows
`tractor`'s infect-asyncio mode runs an `asyncio` loop under
`trio` guest-mode; on Windows the default `ProactorEventLoop` is
incompatible and the suite hangs/crashes mid-run (orphaned py
procs), so the `windows-latest` CI leg never finishes reporting.

- add a module-level `pytest.skip(allow_module_level=True)` gated
  on `platform.system() == 'Windows'` to `test_infected_asyncio`
  and `test_root_infect_asyncio`, before their asyncio-interop
  imports. macOS/linux are unaffected (they run these fine).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-14 23:12:02 -04:00
Gud Boi 1691be96fd Scale `test_lifetime_stack` deadline for CI
`test_lifetime_stack_wipes_tmpfile` guards spawn+teardown with a
hard-coded `trio.move_on_after()` (1.6s / 1s) that isn't scaled
for slow CI. On a noisy macOS runner the `error_in_child=True`
case times out before the child error propagates, so the scope
cancels and `assert not cs.cancel_called` flips — reddening the
(required) macOS leg. Same unscaled-deadline class `main` already
fixed for `test_dynamic_pub_sub`.

- multiply the budget by `cpu_perf_headroom()` (`tests/conftest`),
  the established deadline-headroom helper (3x on macOS CI, a
  1.0 no-op locally / on un-throttled linux).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-14 23:12:02 -04:00
Gud Boi 36ad1f3dd0 Skip `test_ringbuf` at collection off-linux
`tests/test_ringbuf.py` imports `tractor.ipc._ringbuf` at module
top, which pulls in `tractor.ipc._linux` whose module-level
`ffi.dlopen(None)` raises `OSError` on Windows (and any non-linux
host). That fires at COLLECTION, before the module's existing
`pytestmark = pytest.mark.skip` can apply, so it aborts the whole
pytest session — the new `windows-latest` CI leg never gets past
collection.

- guard the module with `pytest.skip(allow_module_level=True)`
  gated on `platform.system() != 'Linux'`, placed before the
  crashing import — same idiom as `tests/devx/test_debugger.py`.
- the `eventfd`-based ringbuf backend is linux-only by design, so
  macOS skips cleanly too (previously it only skipped incidentally
  via the absent `cffi` optional dep).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-14 23:12:02 -04:00
Gud Boi f75c9cdeab Traverse `BaseExceptionGroup` peer-close errors
`_peer_closed_errno()` followed cause and context links but did not
descend through grouped exceptions. A reset below a group could
therefore escape as `trio.BrokenResourceError` instead of the
normalized `TransportClosed` boundary.

Walk the exception tree with cycle protection, requiring every
group branch to represent peer closure before normalization. Extend
the `MsgpackTransport.send()` regression to prove all-transport and
mixed-failure behavior.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 18:54:56 -04:00
Gud Boi 75cda1933c Move registrar probes to `discovery._api`
Registrar election and multi-address probing are discovery-protocol
concerns, but their implementation lived in root-runtime ignition.
Move the bounded handshake probe and its concurrent address
classifier into `discovery._api`, leaving `open_root_actor()` to
consume the classified results.

Update probe tests for the canonical module and clarify that the
daemon-fixture regressions directly exercise their sibling
`conftest` plugin.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 18:43:36 -04:00
Gud Boi f454cefe56 Enforce `get_rt_dir()` ownership on POSIX
Linux previously accepted a pre-existing runtime bindspace without
checking its owner or mode, even though Darwin enforced both. Share
the POSIX directory guard so every managed root and subdir rejects
non-directories and foreign UIDs before changing permissions.

Normalize owner-controlled bindspaces to `0o700` and add Linux
regressions for mode repair, foreign ownership, and non-directory
paths.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 18:40:23 -04:00
Gud Boi d0cc06815f Accept raced `TransportClosed` diagnostics
The `@context` debugger E2E intentionally closes its channel.
Teardown can surface from either local error shipment or the peer
receive task.
The RPC response fix makes local close win under CI, while the test
required both scheduler-dependent diagnostics.

Keep the common debugger and cancellation assertions, then accept
either transport-close report. This preserves real actor-tree
teardown coverage without depending on task scheduling order.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 12:49:40 -04:00
Gud Boi ebf2258b4f Keep accepted RPCs alive on `TransportClosed`
An RPC caller can close its channel after the callee creates the
endpoint coro but before its `StartAck` or final response lands.
Treat those response-send failures as terminal delivery failures so
the accepted endpoint still runs and application errors stay local
instead of cancelling the shared service nursery.

Register each cancellable RPC in `Actor._rpc_tasks` before
publishing its `Context` through `TaskStatus.started()`.
This closes the checkpoint-free completion race without a
cross-task handoff and keeps `Actor._ongoing_rpc_tasks` balanced
through existing cleanup.

Add regressions for caller disconnects at `StartAck` and error
shipment, plus registration-before-execution and cleanup checks.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 12:28:56 -04:00
Gud Boi ac61d7a5bf Use a short UDS reaper test bindspace
Darwin's pytest `tmp_path` can already exceed the 104-byte AF_UNIX
budget before appending either synthetic socket filename. That made
the new sentinel-policy regression fail identically in both macOS
matrix legs without exercising reaper behavior.

Allocate the test's real socket files under a short
`/tmp/tractor-reap-*` directory and retain scoped cleanup.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 10:59:07 -04:00
Gud Boi 584ea4e9ad Document stable macOS UDS operation
The remediation grew beyond the original no-autobind fix, leaving
public docs and nearby comments describing connect-only discovery,
XDG-only socket paths, raw readiness probes, and old cleanup naming.

Document typed registrar probing, occupied-address rejection, Darwin's
short runtime directory, platform-aware socket cleanup, sentinel
readiness, and subprocess output draining. Add the GH #473 bugfix
fragment and update regression rationale without changing task states.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 10:37:19 -04:00
Gud Boi 16dd876b0c Make UDS reaping platform-aware
Moving Darwin sockets to `/tmp/tractor-<uid>` left pytest and the
standalone reaper searching only `XDG_RUNTIME_DIR`. Enabling that
shared bindspace naively would also let automatic session cleanup
unlink an independent live `registry@1616.sock`.

Resolve the runtime's actual default UDS bindspace on every platform.
Exclude the pid-less registry sentinel from automatic cleanup while
retaining explicit CLI removal, and document that destructive choice.

Cover bindspace resolution and automatic-vs-explicit sentinel policy.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 10:36:27 -04:00
Gud Boi 1a9ce915f3 Harden inbound actor handshakes
Registry probes need short retry deadlines, but applying their
one-second budget to every portal and child connection can terminate a
valid delayed actor with no client retry path.

Give ordinary pre-registration handshakes an independent ten-second
deadline. Normalize raw `msgspec.DecodeError` frames to
`TransportClosed` so malformed peers cannot cancel the shared IPC
nursery with decoder internals.

Cover malformed frames and the ordinary-vs-probe timeout distinction.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-14 10:34:59 -04:00
Gud Boi 0b63af020e Retry transient registrar handshakes
A loaded runner can accept a registry transport while delaying its
actor handshake beyond the first one-second attempt. Treating that
timeout as final classifies a healthy daemon as occupied and cascades
into unrelated discovery failures.

Retry connected handshake failures on fresh channels under a shared
three-second budget, while returning immediately for truly absent
listeners. Bound every connect-plus-handshake attempt, use incremental
backoff, and retain the one-second unauthenticated server limit.

Also bound shielded probe-channel cleanup to 200ms and cover the real
timeout, reconnection, backoff, fresh-channel, and stalled-close paths.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 23:40:12 -04:00
Gud Boi 6e424d4696 Use a daemon-ready sentinel in discovery tests
The discovery `daemon` fixture probed UDS readiness by connecting and
immediately closing. That entered Tractor's actor handshake without an
`Aid` payload and destabilized the remote registrar on macOS before
test roots attempted discovery.

Run the child through a small `open_root_actor()` wrapper and publish a
filesystem sentinel only after runtime startup completes. Poll that
sentinel with process-liveness checks and guaranteed setup-failure
cleanup, without touching the transport socket.

Cover transport-free readiness and deterministic polling backoff.

Caught-during: review remediation
Found-via: `/run-tests` discovery daemon fixture consumers
Cause: initial sentinel drafts mishandled `pytest.Testdir`, emitted
  invalid `python -c` syntax, and misplaced return-code logging.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 20:38:22 -04:00
Gud Boi 5724c0516a Probe registrar capability in actor handshakes
Transport connect alone can select a foreign, stalled, or ordinary
actor endpoint as the registry. On macOS UDS this also exercises a
fragile connect-and-bail path before every root election.

Extend `Aid` with backward-compatible probe and registrar capability
fields, require a bounded typed handshake, and classify addresses as
absent, occupied, or confirmed registrars. Reject occupied endpoints
instead of binding over them.

Also,
- close `_connect_chan()` in `finally`
- bypass normal peer tracking for election probes
- preserve legacy registrar handshakes with unknown capability
- cover foreign listeners and idle registrar peer state

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 20:04:48 -04:00
Gud Boi 0d6d7c2a63 Keep UDS post-kill cleanup best-effort
`unlink_uds_bind_addrs()` reconstructs self-assigned socket paths
after a hard kill. An over-budget bindspace can make
`UDSAddress.get_sockname()` raise before the guarded `os.unlink()`,
replacing the original supervision outcome after the child is gone.

Catch and report reconstruction failures, then skip cleanup without
raising. Cover the overflow path and prove no unlink is attempted.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 18:57:13 -04:00
Gud Boi bd38204fde Preserve docs-example body failures
Best-effort subprocess teardown must not replace the exception raised
by the test body. Suppress cleanup errors only while propagating that
active failure; keep raising teardown errors on normal body exit.

Also close the Windows stdin pipe after its bounded leader reap.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 18:41:20 -04:00
Gud Boi 340d506940 Bound generated UDS socket paths
Actor names and custom runtime subdirs can otherwise produce unsafe
or overlong pathname sockets after moving Darwin's bindspace to
`/tmp`.

Deats,
- hash unsafe or over-budget actor names while retaining `@pid.sock`
- share deterministic naming with post-kill socket cleanup
- enforce Linux and Darwin `sun_path` byte budgets
- restore non-Darwin dir validation and mode `0700`
- validate every nested Darwin runtime-dir component

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 18:40:55 -04:00
Gud Boi 64e820e18e Normalize peer resets in `.send()`
A raw UDS readiness client can disconnect before the actor handshake.
Darwin reports the first server write as `ECONNRESET`, wrapped in
`trio.BrokenResourceError`; letting it escape cancels the daemon's
shared IPC nursery and makes later roots elect themselves registrar.

Walk the exception chain for `EPIPE` or `ECONNRESET` and translate
either into the existing `TransportClosed` boundary. Also handle
argument-less resource errors without raising `IndexError`.

Review: PR #480 (goodboy)
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 18:30:44 -04:00
Gud Boi 0a3b0efcc6 Reap timed-out docs example trees
Always drain docs-example pipes, even when the process exited before
the first status check, and decode malformed output with replacement
so diagnostics preserve the original failure.

Run POSIX examples in dedicated sessions and kill the full process
group on timeout. Reap the leader in every exit path, with bounded
Windows cleanup that cannot wait forever on descendant-held pipes.

Cover fast non-zero exits, invalid output bytes, process-group
termination, and post-timeout reaping.

Review: PR #480 (goodboy,copilot-pull-request-reviewer[bot])
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 16:40:05 -04:00
Gud Boi 634d914161 Use short Darwin UDS runtime paths
`platformdirs` can place the runtime dir deep below a pytest temp
home, pushing `registry@1616.sock` past Darwin's 104-byte
`AF_UNIX` limit.

Use a compact `/tmp/<app>-<uid>` root on Darwin and secure it
before allocating socket paths:
- require a real, current-user-owned dir via `lstat()`
- tighten existing roots to mode `0700`
- reject symlinks and unsafe nested dirs

Cover the path budget, mode, and symlink rejection.

Caught-during: review remediation
Found-via: `/run-tests` test_macos_rt_dir_fits_uds_path_limit

Review: PR #480 (goodboy,copilot-pull-request-reviewer[bot])
https://github.com/goodboy/tractor/pull/480

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 16:39:32 -04:00
Gud Boi 3d02a8569e Run UDS-on-macOS CI leg, un-skip the example
UDS-on-macOS is otherwise un-exercised: the matrix
explicitly excludes the `macos-latest` + `uds` combo, so the
`uds_transport_actor_tree.py` example (skipped on macOS CI
since PR #460) is the only thing that ever touches that
path.

- drop the matrix `exclude` so the full suite runs with
  `--tpt-proto=uds` on `macos-latest`.
- un-skip the example on macOS+CI; with example-stderr
  surfacing in place a still-red run now yields the full
  traceback GH #473 asks for instead of a bare returncode
  assert.

Task-bullets 3 + 4 of GH #473.

Prompt-IO: ai/prompt-io/claude/20260702T155006Z_65bf9df5_prompt_io.md

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-13 15:46:56 -04:00
Gud Boi 18faffcff7 Surface example stderr on any non-zero exit
The docs-example harness only re-raises captured subproc
stderr when the LAST line contains 'Error', but a `tractor`
root-actor crash always ends stderr with the strict-EG
collapse note `( ^^^ this exc was collapsed from a group ^^^ )`
— so every possible crash is swallowed down to a bare
`assert 1 == 0`, exactly what the macOS CI leg shows for the
UDS example in GH #473.

- raise with the FULL stderr (+stdout) whenever the example
  subproc exits non-zero, regardless of stderr shape.
- keep the legacy last-line 'Error' check for zero-rc runs
  which still emit error-ish output.

First task-bullet of GH #473.

Prompt-IO: ai/prompt-io/claude/20260702T155006Z_65bf9df5_prompt_io.md

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-13 15:46:56 -04:00
Gud Boi 3705bbe594 Guard the cold import latency budget
Run seven fresh interpreters and gate the median `import tractor`
time below a conservative 0.35s ceiling. Provide an explicit
environment override for platforms with a different baseline.

Also assert the optional modules deferred by this patch remain
unloaded so a timing pass cannot hide an eager-import regression.

Review: PR #478 (goodboy)
https://github.com/goodboy/tractor/pull/478#pullrequestreview-4922213201

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-13 02:35:07 -04:00
Gud Boi 66ac7863b5 Advertise the lazy `to_asyncio` API
Preserve the existing public wildcard surface while adding the lazy
`to_asyncio` submodule to `__all__` and `dir(tractor)`.

Exercise both APIs in cold interpreters and verify normal package
import still leaves `asyncio` unloaded.

Review: PR #478 (goodboy)
https://github.com/goodboy/tractor/pull/478#pullrequestreview-4922213201

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-12 20:55:41 -04:00
Gud Boi 089e158da9 Retain dynamic logger caller discovery
Keep the `sys.modules` lookup as the normal fast path, then use
`inspect.getmodule()` for unregistered `runpy`, plugin, and `exec()`
namespaces.

Cover an unregistered module name backed by a real package file and
verify implicit logger naming still resolves to that package.

Review: PR #478 (goodboy)
https://github.com/goodboy/tractor/pull/478#pullrequestreview-4922213201

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-12 20:52:47 -04:00
Gud Boi 7987f6b8d7 Keep lazy annotations runtime-resolvable
Provide import-free runtime aliases for annotation-only actor and
multiaddr types so `typing.get_type_hints()` remains usable without
eagerly loading optional dependencies.

Correct `_address_types` to its actual `dict` shape and cover the
affected discovery and transport APIs.

Caught-during: review remediation
Found-via: `/run-tests` test_lazy_annotation_names_resolve

Review: PR #478 (goodboy)
https://github.com/goodboy/tractor/pull/478#pullrequestreview-4922213201

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-12 20:52:15 -04:00
Gud Boi ae67e2f429 Assert cancellation at the startup boundary
Replace the pre-start wall-clock sleep with an indefinite
checkpoint so cancellation ordering cannot race a timer in slow CI.

Record that `Cancelled` escapes each `start_or_cancel()` await
before the enclosing nursery or cancel scope handles it.

Review: PR #479 (goodboy)
https://github.com/goodboy/tractor/pull/479

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-12 15:45:30 -04:00
Gud Boi 1c7d0c7f3e Match Trio's startup error exactly
Compare the complete canonical `Nursery.start()` protocol error
before re-surfacing ambient cancellation. Preserve child-owned
`RuntimeError` objects whose messages only resemble Trio's wording.

Cover the colliding prefix and assert the original error remains the
exception group's sole leaf.

Review: PR #479 (goodboy)
https://github.com/goodboy/tractor/pull/479

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-12 15:45:23 -04:00
Gud Boi 203a0f7e1f Add `.trionics.start_or_cancel()` test suite
Resolve #474 with a new `tests/trionics/test_taskc.py` (9
tests) covering the `trio.Nursery.start()` wrapper landed in
PR #464, incl. the `modden.runtime.progman.open_wks()` use
case dug out as a minimal repro.

Deats,
- the lossy `RuntimeError('child exited without calling
  task_status.started()')` only fires when the child absorbs
  its ambient cancel pre-`.started()` (graceful-teardown
  pattern); a well-behaved child surfaces `Cancelled` direct
  from `.start()` on `trio` 0.29 - verified empirically 1st.
- `test_sibling_err_not_masked_by_startup_rte`: the `modden`
  case; ONLY the root-cause sibling `ValueError` escapes the
  nursery with the wrapper vs. bare-`.start()`'s lossy
  riding-along startup-RTE noise.
- `test_pure_oob_cancel_not_morphed_to_rte`: plain ancestor
  `cs.cancel()` exits clean vs. bare's eg-wrapped RTE.
- `test_genuine_startup_rte_still_raised`: sans cancellation
  the protocol-bug RTE re-raises same as bare.
- `test_childs_own_rte_never_demoted_to_cancel`: exact-msg +
  `isinstance`-guard regression cover; a child's own
  `RuntimeError('never got started!')`/`RuntimeError(1234)`
  never demotes to a `Cancelled`.
- `test_started_value_and_args_passthru`: positional args,
  `name=` and `.started()`-value forwarding.

Also,
- `use_start_or_cancel=False` params pin upstream `trio`'s
  current lossy behaviour as wart-documentation: a break on
  a `trio` upgrade likely means new upstream porcelain and
  the wrapper deserves a re-audit.
- verified 0 flakes over 50 hammer runs + 2 impl mutations
  each caught by exactly the targeted tests.

Prompt-IO: ai/prompt-io/claude/20260702T161624Z_65bf9df5_prompt_io.md
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-08-12 14:15:31 -04:00
Gud Boi 67280c2898 Honor log disable controls in hot-path guards
Use `Logger.isEnabledFor()` in `at_least_level()` so logger-local
and global disable controls short-circuit payload rendering.

Add `Channel.send()` coverage for effective-level, per-logger, and
global suppression while ensuring transport remains unchanged.

Caught-during: review remediation
Found-via: `/run-tests` test_log_guard_skips_payload_formatting

Review: PR #458 (goodboy)
https://github.com/goodboy/tractor/pull/458#issuecomment-5258207470

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-08-11 21:56:27 -04:00
Gud Boi a753625fc6 Wait for ctx exit in `maybe_open_context()`
Block the final user on its cached resource's `__aexit__()` and
raise regular cleanup errors at that user's ctx boundary.

Deats,
- serialize user registration and teardown under each cache-key lock
- keep queued entrants on the same lock through resource replacement
- preserve `Cancelled`, `KeyboardInterrupt`, and `SystemExit` flow
- cover successful, failing, cancelled, and re-entry teardown paths

Prompt-IO: ai/prompt-io/opencode/20260804T030309Z_65bf9df5_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-03 23:45:42 -04:00
Gud Boi 51326f4b7a Fix uds `get_random` reaper-regex break (+2 nits)
Copilot's 2nd-pass review (PR #468) caught a regression I landed in
the uds fix: `UDSAddress.get_random()` put the per-call token AFTER
`@{pid}` (`no_runtime_*@{pid}.{token}.sock`), which breaks the
`tractor._testing._reap` matcher
`^(?P<name>.+)@(?P<pid>\d+)\.sock$` — so no-runtime orphan socks
stopped matching and never got reaped/attributed. Move the token
INTO the name (`{prefix}.{token}@{pid}.sock`) so the canonical
`@{pid}.sock` suffix stays intact for both that regex and the
`spawn._reap` reconstruction; also bump the token to 8 hex chars.

Also two robustness nits from the same review,
- `tests.conftest._measure_sustained_headroom()`: guard `frac <= 0`
  before `1./frac` — a 0/parked-core freq read would
  `ZeroDivisionError`, get swallowed by the broad `except` into a
  1.0 (no-throttle), defeating the probe on the exact broken box it
  should flag; read 0 as max throttle.
- `scripts/cpu-perf-check`: mark the burn procs `daemon=True` and
  wrap sampling in `try/finally` so a Ctrl-C / error reaps them
  instead of leaving stray CPU hogs.

Regressed-by: 09c50f49 (uds no-runtime token placed after `@pid`)
Found-via: Copilot review #4595803812 (`_testing._reap` regex)
Review: PR #468 (Copilot)
https://github.com/goodboy/tractor/pull/468#pullrequestreview-4595803812

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-29 18:51:26 -04:00
Gud Boi 63498a80c2 Use `cpu_perf_headroom()` at remaining deadlines
Finish TODO #2 from PR #468: the last raw `cpu_scaling_factor()`
call-sites still used the static-only check (blind to the
sustained-load power-cap). Point them at the `cpu_perf_headroom()`
SUPERSET — it calls `cpu_scaling_factor()` internally + the
session-cached throttle probe, so it's always >= the old factor
(never LESS headroom, so CI stays green).

Migrated,
- `test_spawning`: the `fail_after(1 * ...)` smoke deadline
  (#465-added).
- `test_inter_peer_cancellation`: `this_fast` budget.
- `test_docs_examples`: example-run `timeout`.
- `test_resource_cache`: fan-out `timeout`.

`test_cancellation` already used `cpu_perf_headroom()`; only its
explanatory comments still name the static helper.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-29 18:13:47 -04:00
Gud Boi 5b1a9edeb3 Fix `_burn` bigint blowup, ctx-mgr `_read_mhz`
Both `_burn()` impls (the `cpu-perf-check` script + the
`_measure_sustained_headroom()` probe in `tests.conftest`) grew `x`
~x**2 per iter, so the loop quickly went bigint alloc/mul-bound — a
noisy CPU load + needless memory across N procs. Mask each step to
64-bit for a steady, fixed-width ALU burn (still pegs every core,
which is all the freq probe needs).

Also, `_read_mhz()` opened the sysfs freq files without a ctx-mgr;
`with open(...)` so the FD closes deterministically (matches the
script's own `_read()`).

Review: PR #468 (Copilot)
https://github.com/goodboy/tractor/pull/468

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-29 18:08:50 -04:00
Gud Boi 707caf63d1 Use `cpu_perf_headroom()` for timing-test deadlines
`cpu_perf_headroom()` is a strict SUPERSET of `cpu_scaling_factor()`
— it folds in static cpu-freq scaling + the slow-CI bump AND the
sustained-load power-cap throttle probe. Point the timing-deadline
call sites at it so they're robust to sustained throttling too (the
gremlin behind mass `trio` deadline-miss flakes),

- `test_legacy_one_way_streaming`: `time_quad_ex` cancel-deadline +
  `test_a_quadruple_example`'s `this_fast` smoke bound.
- `test_cancellation`: `test_cancel_via_SIGINT_other_task` +
  `test_fast_graceful_cancel_*` deadlines.

`cpu_perf_headroom >= cpu_scaling_factor` always, so never less
headroom (CI stays green); `cpu_scaling_factor()` remains the
internal static component.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-29 18:08:50 -04:00
Gud Boi 333af7debd Add `cpu_perf_headroom()` for throttle-aware deadlines
Mass `trio` deadline-miss failures on byte-identical code turned
out to be a firmware/EC power-cap (AMD PPT/STAPM) clamping the
all-core sustained clock while every static knob (`governor`,
`scaling_max_freq`, EPP, platform-profile) still read "performance"
— invisible to the existing `cpu_scaling_factor()` check. See
`scripts/cpu-perf-check` + the
`ai/conc-anal/trio_033_cancel_cascade_slowdown_depth3_issue.md`
notes.

Deats,
- add `_measure_sustained_headroom()` to `tests/conftest.py`: a
  one-shot ~0.9s all-core burn (explicit `fork`-ctx `mp` procs)
  sampling achieved-vs-max freq AFTER the boost window; under a 0.6
  gate it returns the full inverse fraction (capped 4x), else 1.0;
  best-effort 1.0 on non-linux or any error,
- add `cpu_perf_headroom()`: `max()` of the static scaling factor
  and the (session-cached) sustained probe,
- inflate deadline budgets by it in `test_dynamic_pub_sub`, both
  `test_clustering` cases, the
  `test_multi_nested_subactors_error_through_nurseries` pexpect
  waits + `test_nested_multierrors`,
- `xfail(strict=False)` `test_nested_multierrors` depth=3 under
  throttle: the deep tree trips tractor's INTERNAL reap deadlines
  (`soft_kill`/`hard_kill` `terminate_after=1.6`) minting a
  `Cancelled` inside the runtime — not fixable by test-budget
  inflation; auto-clears once the box un-throttles.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-29 18:08:50 -04:00
Gud Boi ca78eb2bed Skip the UDS example on macOS CI
`uds_transport_actor_tree.py` (new in 759d46dd) reliably fails on
macOS CI with a bare `returncode 1`. The example forces
`enable_transports=['uds']`, and UDS-on-macOS is otherwise
un-exercised by the matrix (there's no `tpt_proto=uds` macOS job),
so it's the first thing to surface a macOS UDS-path issue.

It passes cleanly on Linux, and the peer-cred paths are already
platform-guarded + graceful (`get_peer_info`/SO_PEERCRED on linux
vs `get_peer_pid`/LOCAL_PEERPID elsewhere) - so the crash is deeper
in the macOS UDS stack. The test swallows the subproc stderr (only
re-raises when the last line has "Error"), so the exact failure
can't be pinned from CI logs; needs a macOS box to root-cause.

Skip it on `_friggin_macos and ci_env` (same shape as the existing
`full_fledged_streaming_service` skip) so #460 lands green; the
macOS UDS root-cause is a follow-up.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-29 13:55:25 -04:00
Gud Boi bffeba952a Use `aid.uid` over manual `(name, uuid)` tuples
The `.uid`->`.aid` migration (prior commits) re-assembled the
legacy uid pair by hand as `(x.aid.name, x.aid.uuid)`.
`Aid.uid` (`msg/types.py`) is the non-deprecated canonical
property returning exactly that pair, so swap to `x.aid.uid`
everywhere — byte-identical, just DRY + consistent.

Covers the 5 review-flagged source sites (`msg._ops`,
`devx.debug._sync`, `experimental._pubsub`, `_exceptions`)
plus the ~22 unflagged identical sites (`_streaming` f-string
logs + 6 test modules); also tightens the `_exceptions`
`our_uid` annotation to `tuple[str, str]`.

Review: PR #469 (copilot-pull-request-reviewer)
https://github.com/goodboy/tractor/pull/469#pullrequestreview-4575979601

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-27 21:38:30 -04:00
Gud Boi dc8562cf14 Adapt SIGINT test to `trio` strict exception-groups
`test_cancel_via_SIGINT_other_task` opened its nursery with the
now-deprecated `strict_exception_groups=False`. Under strict EGs
(trio's default since 0.25) the SIGINT-induced `KeyboardInterrupt`
surfaces wrapped in a `BaseExceptionGroup` — alongside the child
tasks' `Cancelled`s, so it's MULTI-exc and `collapse_eg()` can't
fold it back to a bare KI (the `# why no work!?` the author hit).

Drop the deprecated flag, use a default (strict) nursery, and
assert the result is a bare `KeyboardInterrupt` OR a
`BaseExceptionGroup` whose `.subgroup(KeyboardInterrupt)` is
non-empty.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-25 19:09:42 -04:00
Gud Boi 4607090a5d Use `.aid` fields over deprecated `.uid` in tests
Mirror the core swap on the test side: every `Channel`/`Actor`
`.uid` access now reads the non-deprecated `.aid` struct's
`(.name, .uuid)` (or `.aid.name` for a `.uid[0]`), keeping the
compared / printed value byte-identical while silencing the
self-inflicted `DeprecationWarning`. Touches
`test_context_stream_semantics`, `test_spawning`, `test_local`,
`test_advanced_streaming`, `msg.test_ext_types_msgspec`,
`discovery.test_multi_program`, `test_infected_asyncio`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-25 17:44:22 -04:00
Gud Boi 725c4ef212 Use `.cancel_called` over deprecated `ActorNursery.cancelled`
`tests.test_cancellation` asserted on `ActorNursery.cancelled`,
which now warns + redirects to `.cancel_called`. Swap to the
non-deprecated property so the suite stops tripping its own
`DeprecationWarning`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-25 17:32:54 -04:00
Gud Boi c4dee6ab4b Raw-string pexpect patterns to kill `SyntaxWarning`s
`tests.devx.test_tooling`'s end-of-tree `expect()` patterns use
a literal `\(` (pexpect regex), which Python 3.12+ flags as an
invalid escape sequence. Make them raw strings so the `\(` is
preserved verbatim and no `SyntaxWarning` fires.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-25 17:32:54 -04:00
Gud Boi 7f6782b9be Fix `test_simple_context` for `trio>=0.33` groups
Under `trio>=0.33`'s strict exception-groups a lone
`error_parent` arrives wrapped in a 1-exc `ExceptionGroup`
instead of collapsing to the bare exc, so it skips the
`except error_parent` arm and lands in the group arm — where
`is_multi_cancelled()` is (correctly) `False`, tripping the
assert. Also accept `beg.subgroup(error_parent) is not None`.

Review: PR #465 (copilot + self CI-triage)
https://github.com/goodboy/tractor/pull/465#pullrequestreview-4548191641

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-24 18:19:16 -04:00
Gud Boi 35414a42a9 Scale `test_most_beautiful_word` deadline for CI
Flat `trio.fail_after(1)` wraps a whole actor spawn + IPC
round-trip in `test_most_beautiful_word` — sub-second on a
warm box, but slow/noisy CI runners (esp. macOS) blow the
1s deadline with a `trio.TooSlowError`. Scale the budget by
`cpu_scaling_factor()`: a strict `1s` locally (factor
`1.0`), CI/CPU-throttle headroom (~3x) on macOS.

Review: PR #465 (copilot + self CI-triage)
https://github.com/goodboy/tractor/pull/465#pullrequestreview-4548191641

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-24 18:19:16 -04:00
Gud Boi 8fcb9eb7d6 Skip `test_parent_tty_isolated` off-Linux (`/proc`-only) 2026-06-24 18:19:16 -04:00
Gud Boi 14329ec1d6 Strip ANSI + accept `_create(...)` in devx tests
Two version-compat fixes for the `devx` debugger
test-suite, both about matching upstream output that
got more verbose w/ recent lib releases.

ANSI stripping (`tests/devx/conftest.py`),
- Add `ansi_strip(text)` helper + `_ansi_re` pattern
  (regex per https://stackoverflow.com/a/14693789).
- Apply inside `in_prompt_msg()` + `assert_before()` so
  substring matches against REPL/traceback output stay
  robust to color leakage.
- Motivated by py3.13's colored tracebacks +
  `pdbp`/pygments highlighting leaking ANSI even when
  `PYTHON_COLORS=0` is set in the `spawn` fixture (not
  every renderer in the spawned subproc honors it).
- Replaces the longstanding inline TODO that linked
  the SO answer w/o impl'ing.

trio 0.30+ `Cancelled._create(` match (`test_debugger`),
- In `test_shield_pause` swap the two
  `"raise Cancelled._create()"` assertion patterns →
  `"raise Cancelled._create("` (open-paren form, no
  closing).
- trio >=0.30 raises a multi-line
  `raise Cancelled._create(source=.., reason=..,
  source_task=..)` w/ cancel-reason metadata, so the
  legacy bare-`()` form no longer matches. Inline
  comment documents the trio-version pivot.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 3854cf5ecb)
(cherry picked from commit c07cf2546b)
2026-06-24 18:19:16 -04:00
Gud Boi 008824d650 Add `supervise_run_process` to `trionics._subproc`
A `trio.Nursery.start()`-style wrapper around
`trio.run_process()` that surfaces rc!=0 errors
deterministically, ALWAYS isolates the parent
controlling-tty, and optionally live-relays the child's
std-streams to `log.<level>` per-line. Suits both
short-lived test-runners + long-lived daemons.

`supervise_run_process()`,
- Deterministic rc!=0: pass `check=False` to `trio`
  and do our OWN post-drain rc-check from the
  supervisor coro body AFTER `own_tn.__aexit__` — NOT
  inside the internal nursery, since that would
  race-cancel the still-draining relay reader and lose
  stderr lines. (Re)build + raise a BARE
  `subprocess.CalledProcessError`: `.stderr=` for
  programmatic callers + an `add_note()`'d
  `|_.stderr:` block for human teardown logs. No
  nursery-eg-wrapped CPE to `collapse_eg` around.
- Parent controlling-tty isolation: `stdin=DEVNULL`
  always, `stdout=DEVNULL` unless relayed/overridden
  (via `stdout=` kwarg w/ `_UNSET` sentinel so explicit
  `None` = inherit still works). Prevents a spawned
  program from clobbering the launching tty's scrollback
  w/ control-seqs.
- Live per-line relay: `relay_stdout=True`/
  `relay_stderr=True` → relayed to `log.<relay_level>`
  (default `'io'`, our custom level 21). Picked to sort
  just above stdlib `INFO`=20 so it shows at usual
  `info`/`devx` levels yet stays separately filterable;
  `runtime`=15 was REJECTED as a default since it'd be
  silently filtered at usual verbosity — footgun for
  daemon supervisors whose whole point is visibility.
  STREAMED, not buffered-until-exit.
- Non-blocking `tn.start()` semantics: live
  `trio.Process` handed up via
  `task_status.started()` immediately (else
  `tn.start()` would block till child exit, losing
  the long-lived-daemon use case). Supervise/relay bg
  tasks run to completion in this coro.
- `**run_process_kwargs` forwarded verbatim (env, shell,
  cwd, start_new_session, executable, ...); MANAGED keys
  (`stdin`/`stdout`/`stderr`/`check`) win on conflict.
- Crash-handling layer intentionally NOT baked in —
  compose `maybe_open_crash_handler()` ON TOP at the
  call-site.

`_relay_stream_lines()` helper,
- Concurrent pipe-drain reader. MANDATORY whenever piping
  w/o `capture_*` since nothing else drains the OS pipe —
  child blocks on `write()` once kernel buf (~64KiB) fills
  → deadlock.
- Modes (combine freely): `emit`-only live relay,
  `accum`-only silent drain+capture (for the CPE note),
  or both. Per-line splitting handles cross-chunk
  residuals + flushes any trailing un-newline-term'd line
  at EOF.

`_add_stderr_note()` helper,
- Attaches an indented `|_.stderr:` note to a CPE via
  `add_note()` for legible rc!=0 reporting at teardown.

Tests (`tests/trionics/test_subproc.py`),
- Hermetic `trio`-only (no actor-runtime).
- `test_stdout_relayed_per_line`: per-line stdout relay.
- `test_parent_tty_isolated`: child fd1 is OUR pipe (no
  `/dev/pts/*`), fd0 pinned to `/dev/null`.
- `test_no_deadlock_on_big_unnewlined_output`: 200KiB
  no-newline output completes under `fail_after(2)` —
  exercises the concurrent drain (without it, the child
  blocks at ~64KiB).
- `test_stderr_relay_and_cpe_rebuild`: rc!=0 w/
  `relay_stderr=True` → bare `CalledProcessError` w/ the
  `.stderr` note + per-line live relay.
- `test_nonrelay_cpe_note`: rc!=0 w/o relay → same
  deterministic post-drain CPE w/ `.stderr` note (silent
  drain+capture path).

Re-export `supervise_run_process` from `tractor.trionics`.

Prompt-IO: ai/prompt-io/claude/20260601T231429Z_0e3e008b_prompt_io.md

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit f595acc76c)
(cherry picked from commit 6df9ee11bc)
2026-06-24 18:19:16 -04:00
Gud Boi eb7ef3f0bf Hoist proc-title prefix to `_def_prefix` const
Make the sub-actor proc-title prefix a single
authoritative constant (`_proctitle._def_prefix`) so
the reap-recognition markers and `xontrib` banner pick
it up automatically — one place to flip the prefix
shape going fwd.

Deats,
- `_proctitle._def_prefix: str = '_subactor'`. New
  module-level const consumed by everything that needs
  to know the prefix.
- `set_actor_proctitle(actor, prefix=_def_prefix)`:
  takes an explicit `prefix` arg (default = the const)
  so callers can override per-spawn if they want.
- Default proc-title format:
  `'tractor[<reprol>]'` → `f'{prefix}[<reprol>]'`
  i.e. `_subactor[<reprol>]` by default.
- `_testing/_reap.py`: cmdline + comm markers source
  the prefix from `_proctitle._def_prefix` instead of
  the hardcoded `'tractor['`. So
  `_is_tractor_subactor()` tracks the const
  automatically.
- `xontrib/tractor_diag.xsh`: `acli.reap` orphan-mode
  banner now interpolates the
  `_TRACTOR_PROC_CMDLINE_MARKERS` tuple directly so
  the human-readable mode line stays in sync if the
  prefix shape changes again.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 3a45dbd503)
(cherry picked from commit fd8d39c0ce)
2026-06-24 16:20:50 -04:00
Gud Boi bb0b2ffa3c Add `add_log_level()` factory + register `IO`=21
Follow-up to f595acc7 (`supervise_run_process`) which
called `log.io(...)` for std-stream relay assuming an
`IO=21` level existed. Add the registration via a new
factory + tests covering both the factory and the new
level.

`add_log_level()` factory,
- One call wires the four (otherwise hand-synced) pieces:
  - `CUSTOM_LEVELS[NAME]` — drives the `stacklevel` bump
    in `StackLevelAdapter.log()` + `get_logger()`'s
    per-level audit.
  - `logging.addLevelName()` — stdlib name registration.
  - `STD_PALETTE[NAME]` + `BOLD_PALETTE['bold'][NAME]` —
    color entries consumed by `get_console_log()`'s
    `ColoredFormatter` build.
  - Same-named (lowercase) emit method bound on
    `StackLevelAdapter` so `log.<name>('msg')` works +
    `get_logger()`'s per-level method audit passes.
- Idempotent: re-registering an existing name is a
  no-op-ish refresh that won't clobber an already-bound
  method.
- Method binding uses a default-arg `_level=value` so
  the level int is captured (not late-bound across
  multiple registrations).

`IO=21` level (first user),
- Purple. Used by `tractor.trionics._subproc`'s
  std-stream relay (see f595acc7).
- Value 21 picked to sit just ABOVE stdlib `INFO`=20 so
  it's SHOWN BY DEFAULT at usual `info`/`devx` console
  levels — a `runtime`=15 relay would be silently
  filtered (footgun for daemon supervisors whose whole
  point is visibility). Still distinctly labeled +
  filterable.

Tests (`tests/test_log_sys.py`),
- `test_io_custom_level_registered`: validates the IO
  level is fully wired (`CUSTOM_LEVELS`, `addLevelName`,
  both palettes, `StackLevelAdapter.io()` callable);
  emits a record + sanity-asserts `21 >= INFO(20)`.
- `test_add_log_level_pluggable`: registers a fresh
  `XLVL=19` (cyan) via `add_log_level()`, asserts all
  four wires + the bound `xlog.xlvl()` emit, then
  try/finally cleans up the module-global mutations so
  later `get_logger()` audits don't trip on a
  half-removed level.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 7bd7dd50c7)
(cherry picked from commit 93558fe3c9)
2026-06-24 16:20:50 -04:00
Gud Boi 084f0fc404 Give macOS CI extra headroom for cancel-cascade tests
The `cpu_scaling_factor()` CI bump (2x) wasn't enough for the
macOS runners — slower + noisier than linux for our multi-actor
cancel-cascade timing — so `test_nested_multierrors[depth=3]` and
`test_fast_graceful_cancel_*` flaked there (linux + sdist green),

- make the CI headroom platform-aware: 3x on macOS, 2x on linux
  (keeps the proven linux budget; depth=3 inner 12*3=36s still
  fits under the 40s outer wall).
- give `test_fast_graceful_cancel_*` the `cpu_scaling_factor()`
  headroom it was missing entirely (was a bare `timeout=2.9`).

Verified locally w/ `CI=1` (2x linux path; 3x macOS path confirmed
via forced `_non_linux`).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-18 14:54:07 -04:00
Gud Boi c797bcb783 Address Copilot review nits on PR #462
All 5 flagged items were valid (4 real bugs + 1 dead assert),

- fix an inverted `sys.version_info < (3, 14)` guard in
  `ipc._linux` — the "`cffi` has no 3.14 support" import note now
  fires on 3.14+ (where it applies) instead of on older pys.
- use `os.environ.get('PYTHON_COLORS')` in the `sync_bp` example
  so it doesn't `KeyError` when run outside the test harness.
- correct `dump_task_tree()`'s docstring: the `/tmp` + `/dev/tty`
  tee is gated on `write_file`/`write_tty`, not "unconditional".
- tidy the `ActorTooSlowError` message spacing in `cancel_actor`.
- replace a tautological `applied is True or applied is False` in
  `test_patches` with `isinstance(applied, bool)` (the value is
  order-dependent across the module).

Review: PR #462 (copilot-pull-request-reviewer)
https://github.com/goodboy/tractor/pull/462#pullrequestreview-4527179852

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-18 13:37:55 -04:00
Gud Boi 7b518fe4e1 Make `cpu_scaling_factor()` CI-aware for timing tests
GH Actions (and most shared) CI runners are slow + noisy and —
unlike a throttled local box — don't expose CPU-freq scaling via
sysfs, so `cpu_scaling_factor()` read `1.0` and the timing-
sensitive deadlines/asserts that key off it got NO headroom on
CI (a class of `TooSlowError` / `assert diff < this_fast` flakes),

- add a flat `_ci_env` x2 bump inside `cpu_scaling_factor()` so
  every test already using it (quad streaming, SIGINT-cancel,
  docs examples, ...) gets CI headroom for free — compounds with
  any local-throttle factor.
- route the `time_quad_ex` cancel-deadline through it instead of
  a bespoke per-test `ci_env` bump.
- fix a real bug in `test_nested_multierrors`: its OUTER
  `@tractor_test(timeout=10)` was *smaller* than the inner
  `fail_after_w_trace` budget (trio depth=3 = 12s), so the outer
  wall fired first and pre-empted the snapshot-capturing inner
  deadline -> `FAILED` instead of dumping. Bump the outer to `40`
  (> max inner budget) and scale the inner budgets by
  `cpu_scaling_factor()` too.

Verified locally with `CI=1` (quad + both `test_nested_multierrors`
depths + `test_cancel_via_SIGINT_other_task` green).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-18 12:03:30 -04:00
Gud Boi 6a7dea45bb Bump trio `echoserver` cancel timeout 1→4s
Same trio 0.29 → 0.33 cancel-cascade slowdown that hit
`test_nested_multierrors` (ea67f1b6) — bumps the
`trio`-backend (non-debug, non-forking) budget in
`test_echoserver_detailed_mechanics` from 1s → 4s.

- The 1s budget raced the ~1s teardown deadline. On a
  deadline-fire trio 0.33 injects
  `Cancelled(source='deadline')` (cancel-reason
  metadata) that wraps the mid-stream KBI in a
  `BaseExceptionGroup`, breaking the bare
  `pytest.raises(KeyboardInterrupt)` below.
- Bump matches the forking-spawner branch (4s).
- Inline NOTE references the tracking issue
  `ai/conc-anal/trio_033_cancel_cascade_slowdown_depth3_issue.md`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit d7da502d93)
(cherry picked from commit d0144e52cb)
2026-06-17 17:39:44 -04:00
Gud Boi fa0208e65f Bump trio depth=3 cancel timeout 6→12s
trio 0.29 → 0.33 lock bump (c7741bba) slowed the
depth=3 cancel-cascade in `test_nested_multierrors`
from <6s to ~7-8s; the 6s deadline was firing and its
`Cancelled(source='deadline')` (trio 0.33's new
cancel-reason metadata) collapsed a BEG branch,
breaking the `RemoteActorError` assertion downstream.

- Split the `('trio', _)` case-match into per-depth
  arms: `('trio', 1)` keeps 6s (still finishes in
  ~3s); `('trio', 3)` → 12s.
- Updated inline NOTE explains the version pivot +
  links the tracking issue
  `ai/conc-anal/trio_033_cancel_cascade_slowdown_depth3_issue.md`.
- Existing MTF/`subint_forkserver` budgets unchanged.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit ea67f1b67b)
(cherry picked from commit 57b3ea59ea)
2026-06-17 17:39:44 -04:00
Gud Boi b6bf865f5e Fix `get_logger()` collapse of nested sub-pkgs
Strip the trailing `pkg_path` token ONLY when it duplicates the
caller's leaf-*module* name (which the console header already
shows via `{filename}`), instead of blindly dropping the last
token. This keeps genuine, possibly-*nested* sub-PACKAGE parts
addressable as their own sub-loggers.

- detect a true leaf-mod by comparing the caller's `__name__`
  vs `__package__` (a pkg `__init__` has them equal -> its
  trailing token is a real sub-pkg, NOT a leaf to strip).
- `name='devx.debug'` now -> `tractor.devx.debug`, DISTINCT
  from a bare `devx` -> `tractor.devx`; the old unconditional
  `pkg_path = subpkg_path` collapsed both to `tractor.devx` and
  silently broke per-sub-pkg level control via the logging-spec.
- `get_logger(__name__)` leaf-strip still works (cosmetic, bc
  the leaf-mod is in the `{filename}` header field).

Also,
- update the `LogSpec` caveat: sub-PACKAGE granularity now
  addressable at ANY depth; leaf *modules* intentionally aren't
  (they're the `{filename}`); top-level mods (eg. `to_asyncio`)
  still emit on the root logger.
- adjust `test_root_pkg_not_duplicated_in_logger_name` to the
  new literal explicit-`name` contract (no leaf-collapse).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 9c36363b01)
2026-06-17 17:39:44 -04:00
Gud Boi 8bb9df9e06 Lift `--ll`/`--tl` to plugin + `LogSpec` API
Two coupled changes that let downstream projects (eg. `modden`) inherit
the test-harness loglevel plumbing for free via
`tractor._testing.pytest`:

Plugin lift (`tests/conftest.py` → `_testing/pytest.py`),
- mv `pytest_addoption(--ll)`, the `loglevel` autouse
  fixture, and `test_log` fixture out of the test-suite-
  local conftest into the reusable plugin.
- add `--tl`/`--tractor-loglevel` as a DISTINCT flag from
  `--ll`: `--ll` is the consuming-project's OWN app
  loglevel (scoped to its pkg-hierarchy), `--tl` is the
  `tractor.*` runtime loglevel. `--tl` falls back to
  `--ll` when unset (preserves current `tractor`-suite
  behavior).
- add `testing_pkg_name` session fixture (default
  `'tractor'`) — downstream projects override to e.g.
  `'modden'` so `--ll` scopes to their own hierarchy
  instead of `tractor.*`.
- `loglevel` fixture now yields the resolved
  tractor-runtime level (passed to
  `open_root_actor(loglevel=<.>)` by `@tractor_test`)
  AND separately applies `--ll` to the
  `testing_pkg_name` hierarchy when that isn't
  `tractor`. `test_log` scopes the per-test logger to
  `testing_pkg_name`.

`tractor.log` "logging-spec" mini-DSL,
- `LogSpec = str|bool`. Accepted forms:
  - `True` → enable `pkg_name` root at `default_level`
    (fallback `'cancel'`).
  - `False` → no-op.
  - bare level eg. `'info'` → root-logger at that level.
  - `'sub:info,x:cancel'` → per-sub-logger filter-spec;
    each `<name>` is RELATIVE to `pkg_name` (must NOT
    include the pkg-token).
- `parse_logspec()` → `{sublog|None: level}` mapping.
  `None` key = root-logger. Mixed bare-level + filters
  in one spec is rejected w/ a helpful err msg; so is
  embedding the `pkg_name` token in a sub-name.
- `apply_logspec()` → `(primary_level, {name: log})`:
  parses then enables a `colorlog` stderr handler per
  named (sub)logger. Authoritative sub-logger filters
  get `propagate=False` so they don't double-emit
  through a parallel root-level handler.
- !GRANULARITY CAVEAT! sub-logger names match at
  sub-pkg granularity, not leaf-module — so `devx.debug`
  collapses to the same `tractor.devx` logger as a bare
  `devx`, and top-level lib modules (eg.
  `tractor.to_asyncio`) emit under the *root* logger
  rather than a phantom `to_asyncio` child. Documented
  inline on `LogSpec`.

Other,
- `tests/conftest.py` keeps a NOTE pointing to the
  plugin for future-debugging clarity (don't remove
  silently — the lift is the relevant signal).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 19a77708ba)
2026-06-17 17:39:44 -04:00
Gud Boi 4b70b564c8 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

(cherry picked from commit 1cafaecf52)
2026-06-17 17:39:44 -04:00
Gud Boi c8a77fb92b Use trace CM helpers in `test_dynamic_pub_sub`
Replace inline `trio.fail_after` + manual `signal.alarm` guard with the
`_testing.trace` CM helpers that auto-capture a full ptree/wchan/py-spy
diag snapshot to disk on timeout.

Deats,
- inner guard: `trio.fail_after` → `fail_after_w_trace` (async CM,
  captures on `TooSlowError`).
- outer AFK guard: raw `signal.alarm` → `afk_alarm_w_trace` (sync
  CM, captures on `SIGALRM`), only armed under fork backends.
  Extracts `_run_and_match()` helper to keep branching clean.
- bump `fail_after_s` from 4/12 → 8/20 to stop borderline flakes
  while diag harness accumulates evidence.
- drop `_DIAG_CAP_S` var + manual signal import (now internal to
  `afk_alarm_w_trace`).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit bd07a95d80)
2026-06-17 17:39:44 -04:00
Gud Boi 68698afac7 Harden `test_cancellation` for fork-spawner backends
Deats,
- `pytestmark`: enrich `skipon_spawn_backend('subint')` reason with
  conc-anal doc refs + GH#379 link, add `reap_subactors_per_test`,
  `track_orphaned_uds_per_test`,
  `detect_runaway_subactors_per_test` fixtures
- `test_nested_multierrors`: parametrize over `depth` `{1, 3}`, add
  MTF `xfail(strict=False)` with detailed race-window comment
  explaining the BEG shape mismatch, wrap body in
  `fail_after_w_trace` with per-backend timeout budget, bump
  `@tractor_test(timeout=10)`, drop old multiprocessing depth
  special-casing
- `test_multierror_fast_nursery`: wrap in
  `fail_after_w_trace(30.0)`, accept `TooSlowError` in
  `pytest.raises`, surface explicit `pytest.fail` on hang
- `test_cancel_while_childs_child_in_sync_sleep`: swap
  `spawn_backend` param for `is_forking_spawner`, widen
  `fail_after` delay for fork-based spawners
- `test_remote_error`, `test_multierror`,
  `test_cancel_infinite_streamer`, `test_some_cancels_all`: add
  `set_fork_aware_capture` fixture param
- Drop commented-out per-test `skipon_spawn_backend` blocks (now
  covered by module-level `pytestmark`)

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 32955db02e)
2026-06-17 17:39:44 -04:00
Gud Boi 36ad56843e Harden `test_infected_asyncio` for fork spawners
Deats,
- `test_echoserver_detailed_mechanics`: add `is_forking_spawner`
  param, wrap `main()` in `fa_main()` with per-backend
  `trio.fail_after` (4s fork / 1s trio) to cap cancel-cascade
  teardown that compounds under forkserver.
- `test_sigint_closes_lifetime_stack`: swap `start_method` param
  for `is_forking_spawner`, pre-init `tmp_file`/`ctx` to `None` so
  KBI firing before `open_context` body doesn't `UnboundLocalError`,
  add `pytest.fail` guard for the spawn-time IPC race case, arm
  `signal.alarm` AFK-safety cap (10s) under fork backends

Also,
- `pytestmark`: add `track_orphaned_uds_per_test` +
  `detect_runaway_subactors_per_test` fixtures.
- `delay()`: hardcode `return 1e3` at top (debug override still in
  place).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 7ee0dc2e8f)
2026-06-17 17:39:44 -04:00
Gud Boi acb042ec77 Adjust `test_streaming_to_actor_cluster` timeout
For forking spawner backends that is.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit b10011a36e)
2026-06-17 17:39:44 -04:00
Gud Boi 75c87831cb Enrich `pytestmark` in `test_inter_peer_cancellation`
- `skipon_spawn_backend('subint')`: expand reason with specific
  analysis doc refs + GH issue #379 umbrella link.
- add `track_orphaned_uds_per_test` fixture via `usefixtures` to
  blame-attribute UDS sock-file orphans left by SIGKILL cancel
  cascades.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 7d0a53d205)
2026-06-17 17:39:44 -04:00
Gud Boi 105f0c2944 Adjust `test_simple_context` timeout for forking spawner
(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 75d5b4cf7b)
2026-06-17 17:39:44 -04:00
Gud Boi 48a0f2fc49 Add `set_fork_aware_capture`, timeout to msg tests
- `test_ext_types_over_ipc`: wrap `main()` in `fa_main()` with
  `trio.fail_after(2)` + commented `capfd.disabled()` investigation
  (pytest#14444).
- `test_basic_payload_spec`: add fixture param with note on fork-spawner
  hang prevention.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 8aa07a7932)
2026-06-17 17:39:44 -04:00
Gud Boi d70e003184 Add signal-alarm guard to `test_dynamic_pub_sub`
Outer `signal.alarm` cap that fires even when trio's
`fail_after` is blocked by a shielded-await deadlock
(the bug-class-3 hang under MTF backends). Only armed
for fork-based spawners where the bug lives.

Deats,
- `_DIAG_CAP_S = fail_after_s + 5` — slightly larger than the
  trio-native guard so it always loses when the in-band path works.
- `test_log.cancel()` breadcrumbs at each cancel-scope boundary so the
  last-fired breadcrumb names the swallow point on hang.
- try/finally wrapping around each scope level for deterministic
  breadcrumb emission.
- add `is_forking_spawner`, `set_fork_aware_capture` fixture params.
- rework `fail_after_s`: 4s for fork, 12s for trio (was 30/12).

Also,
- `test_sigint_both_stream_types`: `assert 0` -> `pytest.fail()`, add
  TODO re `pytest.raises()`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 10db117864)
2026-06-17 17:39:44 -04:00
Gud Boi f5940de5c0 Add boot-race conc-anal, widen `xfail` to `n_dups=8`
New `ai/conc-anal/spawn_time_boot_death_dup_name_issue.md`
documenting the spawn-time rc=2 race under rapid
same-name spawning against a forkserver + registrar
— the `wait_for_peer_or_proc_death` helper now surfaces
the death instead of parking forever on the handshake
wait.

Also,
- extract inline `xfail` into module-level
  `_DOGGY_BOOT_RACE_XFAIL` marker.
- apply it to `n_dups=8` too (previously bare) bc
  larger N widens the race window enough to fire
  occasionally.
- link to tracking issue #456.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 92443dc4ef)
2026-06-17 17:39:44 -04:00
Gud Boi 9f9a536fa9 Adjust legacy streaming test timeouts for fork+UDS
Forking spawner + UDS transport has different timing
vs `trio_proc` — streaming example completes faster
in some cases, slower in others depending on fork
overhead + sock setup.

Deats,
- add `expect_cancel` param to `cancel_after()`, raise
  `ActorTooSlowError` when cancel scope fires unexpectedly instead of
  silently returning `None`.
- `time_quad_ex` fixture: bump timeout +1 for forking+UDS, explicit
  `ActorTooSlowError` on `None` result instead of bare `assert results`.
- `test_not_fast_enough_quad`: `xfail` for forking+UDS being "too fast"
  (cancel doesn't fire bc streaming finishes before delay).
- add `is_forking_spawner`, `tpt_proto` fixture params throughout.

Also,
- `_testing/pytest.py`: widen `start_method` parametrize and
  `is_forking_spawner` fixture to `scope='session'`.
- `"""` -> `'''` docstring style throughout.
- hoist `_non_linux` to module scope (was redefined locally in two
  places).
- type hints, kwarg-style `partial()` calls.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit d3cbc92751)
2026-06-17 17:39:44 -04:00
Gud Boi 5f2e89ed1f Harden `test_registrar` with reap fixtures, timeouts
Add module-level `pytestmark` applying per-test
`reap_subactors_per_test`, `track_orphaned_uds_per_test`, and
`detect_runaway_subactors_per_test` fixtures — registrar tests stress
discovery roundtrips that historically left orphaned UDS sock-files.

Deats,
- drop unused `say_hello()` fn, keep only `say_hello_use_wait`;
  rename param `func` -> `ria_fn`.
- use `@tractor_test(timeout=7)` instead of separate
  `@pytest.mark.timeout(7, method='thread')` decorator.
- add `with_timeout()` helper, wire into
  `test_subactors_unregister_on_cancel_remote_daemon`.
- uncomment `_timeout_main()` in `test_stale_entry_is_deleted`, use
  configurable `timeout` var + `debug_mode` guard for `tractor.pause()`
  on cancel.
- `dump_on_hang(seconds=timeout*2)` instead of hardcoded `20`.
- fix typo "oustanding" -> "outstanding".

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit abd3950ba6)
2026-06-17 17:39:44 -04:00
Gud Boi 338f0a1463 Add per-actor `setproctitle` via `devx._proctitle`
New `tractor.devx._proctitle` mod sets each
sub-actor's `argv[0]` (and kernel `comm`) to
`tractor[<aid.reprol()>]` — e.g.
`tractor[doggy@1027301b]` — so `ps`/`top`/`htop`
and `acli.pytree`/reaper tooling can identify
actors at a glance without parsing full cmdlines.

Deats,
- `set_actor_proctitle()` wraps the `setproctitle`
  pkg with `ImportError` guard; optional at runtime
  but listed in `pyproject.toml` so default installs
  benefit.
- called early in `_child._actor_child_main()` after
  `Actor` construction, before `_trio_main()` entry.
- tests in `tests/devx/test_proctitle.py`: format
  unit test, `/proc/{cmdline,comm}` integration
  test, negative detection test.

Resolves #457

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit d60245777e)
2026-06-17 17:39:44 -04:00
Gud Boi 6b14c3dbe7 Add dup-name cancel-cascade escalation test
Extend `test_register_duplicate_name` w/ cancel-level log
breadcrumbs and `try/finally` for better diag on the cancel-cascade
hang.

Add `test_dup_name_cancel_cascade_escalates_to_hard_kill` as a
regression test for the TCP+MTF duplicate-name cancel-cascade
deadlock. Spawns N same-name actors, calls `an.cancel()`, and
asserts teardown completes within a `trio.fail_after()` budget that
scales w/ `n_dups`.

Deats,
- parametrize `n_dups` (2, 4, 8) to widen the race window for
  concurrent `register_actor` RPCs.
- `n_dups=4` xfail'd — exposes a separate boot-race bug (doggy
  `rc=2` under rapid same-name spawn), tracked in #456.
- post-teardown asserts all `Portal` chans disconnect, verifying
  hard-kill escalation worked.

Relates to https://github.com/goodboy/tractor/issues/456

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit caebf60f4e)
2026-06-17 17:39:44 -04:00
Gud Boi a0456fece4 Add `enable_transports`/`registry_addrs` proto guard
Raise `ValueError` from `open_root_actor()` when any
`registry_addrs` entry uses a transport proto not in
`enable_transports` — historically this caused a
silent indefinite hang during the registrar handshake
(the actor could never connect to register/discover).

Also,
- update `test_root_passes_tpt_to_sub` to detect a
  proto mismatch between parametrized `tpt_proto_key`
  and CLI `tpt_proto`, asserting the new guard raises
  `ValueError` with expected msg content.
- replace old commented-out notes with a clearer
  explanation of the mismatch foot-gun.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit d036ef7d7f)
2026-06-17 17:39:44 -04:00
Gud Boi d8a398f6f5 Mv `daemon` + `test_multi_program` to `discovery/`
All `daemon` fixture consumers are discovery-
protocol tests now living under `tests/discovery/`.
Move the fixture, its `_wait_for_daemon_ready`
helper, and `test_multi_program.py` into that subdir
so scope matches usage.

Also,
- add `pytestmark` for `track_orphaned_uds_per_test`
  + `detect_runaway_subactors_per_test` to `test_multi_program` as
    regression net.
- drop now-unused `_PROC_SPAWN_WAIT` + `socket` import from root
  conftest.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit c4082be876)
2026-06-17 17:39:44 -04:00
Gud Boi e873bb6164 Replace sleep with active poll in `daemon` fixture
First draft at resolving,
https://github.com/goodboy/tractor/issues/424

`tests.conftest.py.daemon()` previously used a blind
`time.sleep(_PROC_SPAWN_WAIT + uds_bonus + ci_bonus)` to "wait for the
daemon to come up" before yielding the proc to the test.

Two problems:

1. **Racy under load** — sleep is fixed at design time; loaded boxes
   / cold starts / fork-spawn cost spikes blow past it, leading to
   `ConnectionRefusedError` /`OSError: connect failed` flakes in
   `test_register_duplicate_name`.

2. **Wasteful when daemon comes up fast** — happy-path pays the FULL
   sleep regardless. ~3s of dead time per fixture invocation, ~10-20s
   per full suite run.

Replace with `_wait_for_daemon_ready()` — active poll via stdlib
`socket.create_connection` (TCP) or `socket.connect` (UDS) on the
daemon's bind addr, with 50ms backoff and a 10s/15s deadline (CI gets
extra headroom). Daemon-died-during-startup early-exit catches the case
where `_PROC_SPAWN_WAIT` was silently masking daemon startup crashes.

Why stdlib `socket` (Option 2 from the conc-anal doc) instead of
`tractor`'s own `_root.ping_tpt_socket` closure or trio?

- `tractor.run_daemon()` doesn't return from bootstrap until the runtime
  is fully ready to handle IPC, so probing listen-side acceptance is
  sufficient.
- no need to do the full IPC handshake just to validate readiness.
  Sidesteps the `trio.run()` bootstrap cost (~50ms) per fixture too.

`claude`'s verification: 10/10 runs of `tests/test_multi_program.py`
pass on both `--tpt-proto=tcp` and `--tpt-proto=uds`. Per-test wall-time
`test_register_duplicate_name`: 4.31s → 1.10s. Full file: ~12s → 3.27s
per transport.

Doc-tracked at:
`ai/conc-anal/test_register_duplicate_name_daemon_connect_race_issue.md`

Future work — session-scoped trio runtime in a bg thread to share
fixture-side trio operations across many fixtures (currently overkill
for the one fixture that needs it).

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit ec8c4659c4)
2026-06-17 17:39:44 -04:00
Gud Boi 272b6686ae Harden `test_debugger` for forkserver spawners
Use `is_forking_spawner` fixture + gate spawner-
specific expect patterns in nested-error and daemon
tests. Add `set_fork_aware_capture` to multi-sub
tests that need capture-mode awareness.

Deats,
- replace `start_method` param with `is_forking_spawner` bool fixture.
- bump inter-send delay to 0.1s for IPC stability under fork backends.
- gate `bdb.BdbQuit` + relay-uid patterns behind `not
  is_forking_spawner` (not visible under capsys).
- add `expect(child, EOF)` to confirm clean exit.
- switch caught exc from `AssertionError` to `ValueError` in daemon
  test.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 9031605807)
2026-06-17 17:39:44 -04:00
Gud Boi 797d6cf4fa Drop global mutation of `_PROC_SPAWN_WAIT`
In top level `daemon`-fixture that is..

Use a local `bg_daemon_spawn_delay` instead of
mutating the module-level `_PROC_SPAWN_WAIT` —
previously each `daemon` fixture invocation would
permanently add 1.6s (UDS) or 1s (CI) to the
global, inflating delays across the session.

Also, emit a `test_log.warning()` when verbose
loglevel is silently reduced to `'info'`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit c4885f9d99)
2026-06-17 17:39:44 -04:00
Gud Boi 98fdaf343d 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

(cherry picked from commit 0ef549fadb)
(factored: dropped spawn-backend-only paths: ai/conc-anal/infected_asyncio_under_main_thread_forkserver_hang_issue.md)
2026-06-17 17:39:44 -04:00
Gud Boi 836270e995 Adjust `test_shield_pause` for capsys backends
Under `main_thread_forkserver` the bootstrapping
hook switches to `--capture=sys`, so subactor
fd-level output (tree dumps, zombie-reaper msgs)
isn't captured per-test by pexpect. Gate those
expects behind a `no_capfd` check so the test
passes on both capture modes.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 5a9926fc32)
2026-06-17 17:39:44 -04:00
Gud Boi 02177d96c4 Default `--ll` to `None` in test harness
Only override `tractor.log._default_loglevel` when
the flag is explicitly passed — lets per-spawn and
per-example `loglevel` kwargs take effect instead
of being clobbered by the hard-coded `'ERROR'`
default.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 72a0465c52)
2026-06-17 17:39:44 -04:00
Gud Boi b13d06af80 Update debug examples + harden `test_debugger`
Pass explicit `loglevel` to `spawn()` calls in
`test_debugger` tests — required for pexpect
pattern matching now that examples no longer
hard-code log levels.

Also,
- make `expect()` return the decoded `before` str.
- add `start_method` param + fork-backend timeout
  slack (+4s) in nested-error test.
- clean up debug examples: drop unused loglevels,
  rename `n` -> `an`, fix docstrings, add TODO
  comments for tpt parametrize via osenv.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 9431a81d37)
2026-06-17 17:39:44 -04:00
Gud Boi 99098c5a14 Update `sync_bp` + tighten `test_pause_from_sync`
Add `disable_pdbp_color()` to the `sync_bp` example
to suppress pygments prompt coloring when
`PYTHON_COLORS=0` — makes pexpect pattern matching
deterministic.

Deats,
- set `loglevel='pdb'` in both script + test spawn.
- disable `enable_stack_on_sig` in example, assert
  no `stackscope` output in test.
- update `attach_patts` keys/values with `|_<Task`
  / `|_<Thread` / `|_('subactor'` prefixes to match
  actual tree-dump format.
- add call-site patterns (`tractor.pause_from_sync()`
  `tractor.pause()`, `breakpoint(hide_tb=...)`).
- trim trailing `\n` from `Lock.repr()` output.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit fc2e298a29)
2026-06-17 17:39:44 -04:00
Gud Boi e1da7be4d9 Fix `SIGUSR1` tree-dump ordering in `_stackscope`
Factor the sub-actor relay loop out of
`dump_tree_on_sig()` into `_relay_sig_to_subactors()`
and chain both dump + relay in a single
`run_sync_soon` callback (`_dump_then_relay`) so the
parent's task-tree flushes BEFORE any sub receives
the signal — fixes a hierarchical-ordering race
where subs could dump ahead of the parent in the
muxed pty stream.

Also,
- gate file/tty sink writes behind `write_file` +
  `write_tty` params on `dump_task_tree()`.
- use `actor.aid.uid` instead of deprecated `.uid`.
- update `test_shield_pause` expects to match the
  new sequential parent -> relay-log -> sub ordering.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit e2b790a70d)
2026-06-17 17:39:44 -04:00
Gud Boi af72192461 Add `pytest_load_initial_conftests()` for `--capture=`
Move `--capture=sys` enforcement from a static ini
flag to a `pytest_load_initial_conftests()` bootstrap
hook that dynamically flips capture mode only when a
fork-based spawner (like `main_thread_forkserver`) is
detected; non-fork backends keep `--capture=fd`.

Also,
- load `tractor._testing.pytest` via `-p` in ini
  (bc bootstrapping hooks must register before
  conftest `pytest_plugins` runs).
- register `_reap` as sub-plugin via `pytest_plugins`
  tuple in `._testing.pytest`.
- drop now-duplicate reap fixtures (already in `_reap`
  per 1cdc7fb3).
- rename `tractor_enable_stackscope` dest -> `enable_stackscope`
  and pop env var on disable.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 61d4525137)
2026-06-17 17:39:44 -04:00
Gud Boi f9e64d0035 Allow per-call `start_method`/`loglevel` overrides
In `tests/devx/conftest.py::spawn`, refactor the
fixture-internal closures so consumer tests can pass
explicit `start_method`/`loglevel` to each `_spawn()`
invocation rather than only inheriting the fixture-
scoped parametrize values.

Deats,
- promote `set_spawn_method()` and `set_loglevel()`
  to take their respective values as fn params (vs
  closing over the fixture-scope vars).
- give `_spawn()` `start_method=start_method` and
  `loglevel: str|None = None` kwargs so callers
  override one-off without re-parametrizing the
  suite. NOTE: this drops the implicit fixture-
  scoped `loglevel` forward — `_spawn()` callers
  now must pass `loglevel=...` explicitly.
- TODO: figure out how `--ll <level>` should map to
  the default (currently `None` → uses env-var or
  tractor default).
- add a docstring to `_spawn()` so its role as the
  consumer-facing closure is obvious from `help()`.

Also,
- `assert_before()` now returns the `.before` output
  on success (was `None`); add a one-line docstring
  describing the new return contract.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 486249d74f)
2026-06-17 17:39:44 -04:00
Gud Boi 5d37acb5b8 Drop test-local timeouts, +`sync_pause` to dev
In `pyproject.toml`,
- include the `sync_pause` group from `dev`, so dev
  installs ship `greenback` for `pause_from_sync()`.

Comment out per-test `@pytest.mark.timeout(...)`
markers in,
- `tests/devx/test_debugger.py`
- `tests/discovery/test_registrar.py`
- `tests/spawn/test_main_thread_forkserver.py`
- `tests/spawn/test_subint_cancellation.py`
- `tests/test_advanced_streaming.py`
- `tests/test_cancellation.py`

The global cap was already dropped (3c366cac); these
were the leftover per-test caps which now block
interactive `pdb` flows under the new spawn backends.

In `uv.lock`,
- pull `greenback` into the resolved `dev` deps
  (per the `sync_pause` include above).
- catch up the prior `xonsh` editable→PyPI switch
  (from the `pyproject.toml` `tool.uv.sources` edit).

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit b7115fc875)
(factored: dropped spawn-backend-only paths under tests/spawn/)
2026-06-17 17:39:44 -04:00
Gud Boi d4eac06de2 Honor `TRACTOR_LOGLEVEL`+`TRACTOR_SPAWN_METHOD` env-vars
Add env-var overrides inside `._root.open_root_actor()` so
devs/test-runs can swap the actor-spawn backend or crank
console verbosity *without* touching application code.

In `._root.open_root_actor()`,
- read `TRACTOR_LOGLEVEL` early, overriding any caller-passed
  `loglevel` and stashing an `env_ll_report` to emit once the
  console log is set up.
- pull the `loglevel` fallback (`or _default_loglevel`) and
  `log.get_console_log()` init *up* so the env-var report
  routes through tractor's own logger.
- read `TRACTOR_SPAWN_METHOD`, overriding any caller-passed
  `start_method` and warn-logging when the env-var clobbers
  an explicit caller value.

Wire the same vars through `tests/devx/conftest.py::spawn`,
- request the `loglevel` fixture, set both `TRACTOR_LOGLEVEL`
  and `TRACTOR_SPAWN_METHOD` in `os.environ` before each
  `pexpect.spawn()` (inherited by the example subproc).
- expand `supported_spawners` to include
  `main_thread_forkserver` and `subint_forkserver` bc
  example scripts no longer need per-script CLI plumbing.
- pop both vars in fixture teardown so a leaked value can't
  re-route a later in-process tractor test's spawn-backend
  or loglevel.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 208e7c0926)
2026-06-17 17:39:44 -04:00
Gud Boi 950b6bc6b8 Add todo for running `test_debugger` suite on forkserver spawner
(cherry picked from commit 2917b74ba4)
2026-06-17 17:39:44 -04:00
Gud Boi dacd7a7c57 Backend-aware `fail_after` in pub/sub test
Mirror `060f7d24`'s pattern (backend-aware timeout in
`maybe_expect_raises`) for `test_dynamic_pub_sub`'s hard
`trio.fail_after` cap. Fork-based backends pay per-spawn
fork+IPC-handshake cost which stacks over `cpus - 1`
sequential `n.run_in_actor()` calls; empirically 12s
flakes on `main_thread_forkserver` under UDS
cross-pytest contention (#451 / #452).

Defaults:
- `main_thread_forkserver` → 30s
- everything else          → 12s (unchanged)

Hoist the timeout-pick out of the `main()` closure so the
dispatch happens once in the trio task rather than
re-evaluating per spawn.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 383b0fdd75)
2026-06-17 17:39:44 -04:00
Gud Boi c603a6e1a0 Backend-aware timeout in `maybe_expect_raises`
Default `timeout` from `int = 3` → `int|None = None`;
when unset, pick a backend-aware value. Fork-based
backends (`main_thread_forkserver`) need real headroom
bc actor spawn + IPC ctx-exit + msg-validation error
path is much heavier than under `trio` backend —
especially under cross-pytest-stream contention (#451).

Defaults:
- `main_thread_forkserver` → 30s
- everything else          → 3s (unchanged)

Empirical flake history that motivated 30s as the floor
on fork backends (all from `test_basic_payload_spec`):

- 3s  → all-valid variant flaked w/ `TooSlowError`
- 8s  → `invalid-return` variant flaked w/ `Cancelled`
        (surfaced instead of `MsgTypeError` bc the
        outer `fail_after` fired mid-error-path)
- 15s → flaked under cross-pytest-stream contention

30s gives plenty of headroom while still failing-loud
on a genuine hang. Callers can opt out by passing an
explicit `timeout=` kw.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

(cherry picked from commit 060f7d24c4)
2026-06-17 17:39:44 -04:00