Compare commits

...

5 Commits

Author SHA1 Message Date
Gud Boi 023dbf89e4 Fix dropped `for/else` re-raise in masking CM
`30e15925` ("Add `start_or_cancel()` to `trionics._taskc`")
inserted `async def start_or_cancel()` — whose body opens its
own col-4 `try:` — immediately before the trailing `else:
raise`. Because the edit was a pure insertion (0 deletions),
the *same* `else: raise` lines were silently REPARENTED: they
used to be the `for exc_match in matching: ... else: raise`
of `maybe_raise_from_masking_exc`, but now bind to
`start_or_cancel`'s `try/except` where they're unreachable
dead code.

Net effect: `maybe_raise_from_masking_exc` lost the `for/else`
re-raise of the un-masked exception, so a masked child
cancellation gets swallowed instead of surfaced.

- restore the `for/else: raise` to `maybe_raise_from_masking_exc`
- drop the now-dead `else: raise` from `start_or_cancel`

Surfaced as 2 deterministic failures in
`test_sigint_closes_lifetime_stack[wait_for_ctx-bg_aio_task-
send_SIGINT_to=child-*]` (the SIGINT-to-child "silent-abandon"
regime). Bisected with `trio` held at `0.29.0`: clean at
`9c36363b` (0/8), broken at `30e15925` (8/8), fixed (0/8).
NOT a `trio` (0.29↔0.33 identical) nor logging-plugin
regression.

(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 325574cc07)
(cherry picked from commit 0b8033fdaa)
2026-06-22 15:19:34 -04:00
Gud Boi 8952b39ee7 Add `start_or_cancel()` to `trionics._taskc`
Wrapper around `trio.Nursery.start()` that DOESN'T mask
out-of-band cancellation as a lossy startup failure.
Picks the right re-raise: ambient `Cancelled` when
present, the genuine startup-protocol `RuntimeError`
otherwise.

The problem,
- `trio.Nursery.start()` raises a generic
  `RuntimeError("child exited without calling
  task_status.started()")` whenever the started task
  exits BEFORE calling `task_status.started()` —
  INCLUDING the common case where the child was
  cancelled out-of-band by an *ancestor* cancel-scope
  erroring/cancelling.
- In that case the original `trio.Cancelled` is
  swallowed and the caller is left w/ an opaque,
  root-cause-detached `RuntimeError`.

The fix,
- Catch the "...started" RTE.
- `await trio.lowlevel.checkpoint_if_cancelled()` —
  re-raises the in-flight `Cancelled` IFF we're under
  effective cancellation (ancestor-inclusive), carrying
  trio's auto-generated reason which points at the true
  root exc.
- If we're NOT cancelled the `checkpoint_if_cancelled()`
  is a cheap no-op and we fall through to re-raise the
  genuine startup-protocol RTE.

Re-export from `tractor.trionics` so callers don't have
to reach into `_taskc`.

(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 30e15925ba)
(cherry picked from commit 2b4589b1ee)
2026-06-22 15:19:34 -04:00
Gud Boi 94a3413f5c 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-22 15:19:34 -04:00
Gud Boi e4892a8e90 Split py-version-gated uv dependency-groups
Reshuffle `pyproject.toml` deps into per-python-version
`[tool.uv.dependency-groups]`:
- `subints` group: `msgspec>=0.21.0`, py>=3.14
- `eventfd` group: `cffi>=1.17.1`, py>=3.13,<3.14
- `sync_pause` group: `greenback`, py>=3.13,<3.14
  (was in `devx`; moved out bc no 3.14 yet)

Bump top-level `msgspec>=0.20.0` too.

(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 34d9d482e4)
(factored: kept only the pyproject dep-group parts of
 "Raise `subint` floor to py3.14 and split dep-groups"; dropped
 tractor/spawn/_spawn.py + tractor/spawn/_subint.py)
2026-06-22 15:19:34 -04:00
Bd 2fdf3901cb
Merge pull request #462 from goodboy/subint_era_tooling
Subint era tooling
2026-06-19 08:58:12 -04:00
2 changed files with 54 additions and 0 deletions

View File

@ -36,4 +36,5 @@ from ._beg import (
) )
from ._taskc import ( from ._taskc import (
maybe_raise_from_masking_exc as maybe_raise_from_masking_exc, maybe_raise_from_masking_exc as maybe_raise_from_masking_exc,
start_or_cancel as start_or_cancel,
) )

View File

@ -27,6 +27,9 @@ from types import (
TracebackType, TracebackType,
) )
from typing import ( from typing import (
Any,
Awaitable,
Callable,
Type, Type,
TYPE_CHECKING, TYPE_CHECKING,
) )
@ -295,3 +298,53 @@ async def maybe_raise_from_masking_exc(
else: else:
raise raise
async def start_or_cancel(
nursery: trio.Nursery,
async_fn: Callable[..., Awaitable[Any]],
*args,
name: object = None,
) -> Any:
'''
Like `trio.Nursery.start()` but DON'T mask an out-of-band
cancellation as a (lossy) startup failure.
`trio.Nursery.start()` raises a generic
`RuntimeError("child exited without calling
task_status.started()")` whenever the started task exits
BEFORE calling `task_status.started()` INCLUDING the very
common case where the child was cancelled out-of-band by an
*ancestor* cancel-scope erroring/cancelling. In that case the
original `trio.Cancelled` is swallowed and the caller is left
with an opaque, root-cause-detached `RuntimeError`.
This wrapper re-surfaces any ambient (effective, hence
ancestor-inclusive) cancellation via
`trio.lowlevel.checkpoint_if_cancelled()` so the real
`trio.Cancelled` (carrying trio's auto-generated reason which
points at the true root exc) propagates instead. Only when we
are NOT under cancellation is the "didn't call `.started()`"
`RuntimeError` a genuine startup-protocol bug worth surfacing,
so it's re-raised as-is in that case.
'''
try:
return await nursery.start(
async_fn,
*args,
name=name,
)
except RuntimeError as rte:
if (
rte.args
and
'started' in rte.args[0]
):
# re-raises the in-flight `trio.Cancelled` IFF we're
# under effective cancellation; else a cheap no-op and
# we fall through to re-raise the genuine startup RTE.
await trio.lowlevel.checkpoint_if_cancelled()
raise