Compare commits
No commits in common. "78926bc3795499b2fea408eacb4f53612e045117" and "65bf9df5bad583bb30e09b2659781370268a6b64" have entirely different histories.
78926bc379
...
65bf9df5ba
|
|
@ -118,6 +118,11 @@ jobs:
|
||||||
'tcp',
|
'tcp',
|
||||||
'uds',
|
'uds',
|
||||||
]
|
]
|
||||||
|
# https://github.com/orgs/community/discussions/26253#discussioncomment-3250989
|
||||||
|
exclude:
|
||||||
|
# don't do UDS run on macOS (for now)
|
||||||
|
- os: macos-latest
|
||||||
|
tpt_proto: 'uds'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
---
|
|
||||||
model: claude-fable-5
|
|
||||||
service: claude
|
|
||||||
session: b8ec2ffe-1e47-4db5-8bc3-5bebc5feaaf6
|
|
||||||
timestamp: 2026-07-02T15:50:06Z
|
|
||||||
git_ref: 65bf9df5
|
|
||||||
scope: code
|
|
||||||
substantive: true
|
|
||||||
raw_file: 20260702T155006Z_65bf9df5_prompt_io.raw.md
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prompt
|
|
||||||
|
|
||||||
From `ai/prompt-io/prompts/issue_473.md`:
|
|
||||||
|
|
||||||
> NOTE: you MUST pause this work at 12:50PM EST (BEFORE
|
|
||||||
> your weekly limit reset) for review by a human!
|
|
||||||
>
|
|
||||||
> resolve GH #473: https://github.com/goodboy/tractor/issues/474
|
|
||||||
> do it with /open-wkt.
|
|
||||||
|
|
||||||
(The prompt's issue-number/URL mismatch was resolved in
|
|
||||||
favor of #473 — file name, session name and prompt text
|
|
||||||
all say #473; the `/474` URL is a typo.)
|
|
||||||
|
|
||||||
GH #473: "Root-cause UDS-on-macOS —
|
|
||||||
`uds_transport_actor_tree` example fails on CI".
|
|
||||||
|
|
||||||
## Response summary
|
|
||||||
|
|
||||||
Worked all four #473 next-step checkboxes in wkt
|
|
||||||
`uds_macos_473`:
|
|
||||||
|
|
||||||
1. **stderr surfacing**: `test_docs_examples.py` now
|
|
||||||
raises with the full subproc stderr (+stdout) on ANY
|
|
||||||
non-zero exit. The old last-line-'Error' check could
|
|
||||||
never fire for `tractor` crashes since root-actor
|
|
||||||
stderr always ends with the strict-EG collapse note
|
|
||||||
`( ^^^ this exc was collapsed from a group ^^^ )` —
|
|
||||||
proven against the real PR #460 macOS CI log (bare
|
|
||||||
`assert 1 == 0`, no traceback).
|
|
||||||
2. **root-cause (linux-provable layer)**: macOS-only
|
|
||||||
addr corruption in
|
|
||||||
`MsgpackUDSStream.get_stream_addrs()` — no
|
|
||||||
`SO_PASSCRED`/autobind on darwin means the accept
|
|
||||||
side's `getpeername()` is `''`, and the
|
|
||||||
`(str(), str())` arm took `peername` unconditionally →
|
|
||||||
`Path('')` garbage addrs on every accepted conn.
|
|
||||||
Proven + fixed via linux no-autobind simulation.
|
|
||||||
Possibly not the final macOS crasher (non-fatal on
|
|
||||||
linux-sim); the diagnostic patch guarantees the next
|
|
||||||
macOS CI run shows any remaining layer.
|
|
||||||
3. **CI matrix**: removed the `macos-latest`+`uds`
|
|
||||||
exclude.
|
|
||||||
4. **un-skip**: dropped the macOS+CI skip of the example.
|
|
||||||
|
|
||||||
Also: `start_listener()` bindspace mkdir hardened
|
|
||||||
(`parents=True, exist_ok=True`), example docstring
|
|
||||||
peer-pid mechanism corrected for macOS.
|
|
||||||
|
|
||||||
## Files changed
|
|
||||||
|
|
||||||
- `tests/test_docs_examples.py` — surface full stderr on
|
|
||||||
non-zero exit; remove macOS skip of the UDS example
|
|
||||||
- `tractor/ipc/_uds.py` — fix no-autobind
|
|
||||||
`get_stream_addrs()` arm; harden bindspace mkdir;
|
|
||||||
document autobind semantics
|
|
||||||
- `.github/workflows/ci.yml` — add macOS UDS leg (remove
|
|
||||||
matrix exclude)
|
|
||||||
- `examples/uds_transport_actor_tree.py` — platform-
|
|
||||||
correct peer-pid docs
|
|
||||||
|
|
||||||
## Human edits
|
|
||||||
|
|
||||||
None yet — pending human review (hard-stop review
|
|
||||||
checkpoint at 12:50PM EST per prompt); commit staging and
|
|
||||||
any edits are the human's.
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
---
|
|
||||||
model: claude-fable-5
|
|
||||||
service: claude
|
|
||||||
timestamp: 2026-07-02T15:50:06Z
|
|
||||||
git_ref: 65bf9df5
|
|
||||||
diff_cmd: git diff main..wkt/uds_macos_473
|
|
||||||
---
|
|
||||||
|
|
||||||
# Raw output — GH #473 UDS-on-macOS root-cause session
|
|
||||||
|
|
||||||
NOTE: code output is diff-referenced (not copied) per
|
|
||||||
prompt-io diff-ref mode; all generated code is on branch
|
|
||||||
`wkt/uds_macos_473` relative to `main` (65bf9df5).
|
|
||||||
|
|
||||||
## Diagnostic narrative (verbatim)
|
|
||||||
|
|
||||||
Root-cause work proceeded by linux-side simulation since no
|
|
||||||
macOS host is available:
|
|
||||||
|
|
||||||
1. Pulled the actual macOS CI failure log from PR #460 run
|
|
||||||
28343878933 (job 84115107992): the failure is a bare
|
|
||||||
`assert 1 == 0` on `proc.returncode` with NO stderr
|
|
||||||
surfaced — the harness only re-raised when the LAST
|
|
||||||
stderr line contained 'Error'.
|
|
||||||
|
|
||||||
2. Reproduced the stderr-swallow mechanism on linux: any
|
|
||||||
`tractor` root-actor crash ends stderr with the
|
|
||||||
strict-EG collapse note
|
|
||||||
`( ^^^ this exc was collapsed from a group ^^^ )` which
|
|
||||||
never matches 'Error' — so EVERY possible crash was
|
|
||||||
swallowed. (Verified by sabotaging the runtime dir via
|
|
||||||
an over-long `XDG_RUNTIME_DIR` → `OSError: AF_UNIX path
|
|
||||||
too long` → rc=1 + swallowed.)
|
|
||||||
|
|
||||||
3. Found + proved a macOS-only addr-corruption bug in
|
|
||||||
`MsgpackUDSStream.get_stream_addrs()`: the
|
|
||||||
`(str(), str())` match-arm unconditionally took
|
|
||||||
`peername`, but on no-autobind platforms (macOS lacks
|
|
||||||
linux's `SO_PASSCRED`-triggered autobind) the accept
|
|
||||||
side's `getpeername()` is `''` → `Path('')` garbage
|
|
||||||
laddr/raddr on EVERY accepted UDS conn. Simulated on
|
|
||||||
linux by nulling `SO_PASSCRED` (no autobind → same `''`
|
|
||||||
shape): pre-fix the example printed
|
|
||||||
`listener sock file: .`; post-fix it prints the real
|
|
||||||
registry sockpath. Non-fatal on linux-sim (rc=0), so
|
|
||||||
possibly not the final macOS crasher — the diagnostic
|
|
||||||
patch guarantees the next macOS CI run reveals any
|
|
||||||
remaining layer.
|
|
||||||
|
|
||||||
4. Falsified the missing-parent-dir theory:
|
|
||||||
`get_rt_dir()` already `mkdir(parents=True,
|
|
||||||
exist_ok=True)`s at import (and macOS TCP CI passes),
|
|
||||||
so `~/Library/Caches/TemporaryItems` absence cannot be
|
|
||||||
the crasher. Hardened `start_listener()`'s bindspace
|
|
||||||
mkdir anyway (custom `filedir` case + racing actors).
|
|
||||||
|
|
||||||
## Generated changes (diff pointers)
|
|
||||||
|
|
||||||
> `git diff main..wkt/uds_macos_473 -- tests/test_docs_examples.py`
|
|
||||||
|
|
||||||
- always raise with FULL subproc stderr (+stdout) on any
|
|
||||||
non-zero example exit; keep legacy last-line 'Error'
|
|
||||||
check for zero-rc cases; drop the macOS+CI skip of
|
|
||||||
`uds_transport_actor_tree.py` (GH #473 next-step).
|
|
||||||
|
|
||||||
> `git diff main..wkt/uds_macos_473 -- tractor/ipc/_uds.py`
|
|
||||||
|
|
||||||
- `get_stream_addrs()`: document the autobind semantics
|
|
||||||
(bytes = linux abstract-ns autobind artifact), add
|
|
||||||
no-autobind `(str, str)` arm picking the non-empty name
|
|
||||||
(`peername` connect-side, `sockname` accept-side) with
|
|
||||||
an empty-pair `ValueError` guard.
|
|
||||||
- `start_listener()`: `bs.mkdir(parents=True,
|
|
||||||
exist_ok=True)`.
|
|
||||||
|
|
||||||
> `git diff main..wkt/uds_macos_473 -- .github/workflows/ci.yml`
|
|
||||||
|
|
||||||
- remove the `macos-latest`+`uds` matrix exclude so
|
|
||||||
UDS-on-macOS is exercised by CI (GH #473 next-step).
|
|
||||||
|
|
||||||
> `git diff main..wkt/uds_macos_473 -- examples/uds_transport_actor_tree.py`
|
|
||||||
|
|
||||||
- docs nit: peer-pid mechanism is `SO_PEERCRED` on linux,
|
|
||||||
`LOCAL_PEERPID` on macOS.
|
|
||||||
|
|
||||||
## Verification (verbatim summary)
|
|
||||||
|
|
||||||
- macOS-shape sim (no autobind) + fix: example rc=0 with
|
|
||||||
correct listener sockpath.
|
|
||||||
- native linux post-fix: example rc=0, autobind arms
|
|
||||||
unchanged.
|
|
||||||
- sabotage smoke-test: diagnostic patch surfaces the full
|
|
||||||
EG traceback incl. collapse-note line.
|
|
||||||
- `pytest tests/ipc/ tests/test_2way.py tests/discovery/
|
|
||||||
--tpt-proto uds`: 61 passed, 1 `TooSlowError` flake
|
|
||||||
(test_simple_rpc) that passes solo in 2.2s — attributed
|
|
||||||
to a concurrent full-suite run (other session) loading
|
|
||||||
the host.
|
|
||||||
- full `tests/test_docs_examples.py` run pending at
|
|
||||||
raw-file write time (queued behind the concurrent
|
|
||||||
session's suite).
|
|
||||||
|
|
@ -6,8 +6,7 @@ subactor inherits the preference.
|
||||||
|
|
||||||
Every channel address is a filesystem socket path (no TCP port
|
Every channel address is a filesystem socket path (no TCP port
|
||||||
in sight!) and, as a kernel-provided bonus, the peer's pid is
|
in sight!) and, as a kernel-provided bonus, the peer's pid is
|
||||||
exchanged for free via `SO_PEERCRED` on linux,
|
exchanged for free via `SO_PEERCRED`.
|
||||||
`LOCAL_PEERPID` on macOS.
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
import os
|
import os
|
||||||
|
|
@ -43,7 +42,7 @@ async def main() -> None:
|
||||||
# (named for the root registrar) this channel rode in
|
# (named for the root registrar) this channel rode in
|
||||||
# on, NOT a per-child path; the child-specific identity
|
# on, NOT a per-child path; the child-specific identity
|
||||||
# we get for free is the kernel-reported peer pid (via
|
# we get for free is the kernel-reported peer pid (via
|
||||||
# `SO_PEERCRED` on linux, `LOCAL_PEERPID` on macOS).
|
# `SO_PEERCRED`).
|
||||||
print(
|
print(
|
||||||
f'portal chan tpt proto: {raddr.proto_key!r}\n'
|
f'portal chan tpt proto: {raddr.proto_key!r}\n'
|
||||||
f'listener sock file: {raddr.sockpath}\n'
|
f'listener sock file: {raddr.sockpath}\n'
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,21 @@ def test_example(
|
||||||
'This test does run just fine "in person" however..'
|
'This test does run just fine "in person" however..'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
'uds_transport_actor_tree' in ex_file
|
||||||
|
and
|
||||||
|
_friggin_macos
|
||||||
|
and
|
||||||
|
ci_env
|
||||||
|
):
|
||||||
|
pytest.skip(
|
||||||
|
'UDS-transport example reliably fails on macOS CI.\n'
|
||||||
|
'UDS-on-macOS is otherwise un-exercised by the matrix\n'
|
||||||
|
'(no `tpt_proto=uds` macOS job), so this new example is\n'
|
||||||
|
'the first to surface it; the macOS UDS path needs\n'
|
||||||
|
'root-causing. Passes on Linux.'
|
||||||
|
)
|
||||||
|
|
||||||
from .conftest import cpu_perf_headroom
|
from .conftest import cpu_perf_headroom
|
||||||
|
|
||||||
timeout: float = (
|
timeout: float = (
|
||||||
|
|
@ -163,11 +178,10 @@ def test_example(
|
||||||
code = ex.read()
|
code = ex.read()
|
||||||
|
|
||||||
with run_example_in_subproc(code) as proc:
|
with run_example_in_subproc(code) as proc:
|
||||||
out = None
|
|
||||||
err = None
|
err = None
|
||||||
try:
|
try:
|
||||||
if not proc.poll():
|
if not proc.poll():
|
||||||
out, err = proc.communicate(timeout=timeout)
|
_, err = proc.communicate(timeout=timeout)
|
||||||
|
|
||||||
except subprocess.TimeoutExpired as e:
|
except subprocess.TimeoutExpired as e:
|
||||||
test_log.exception(
|
test_log.exception(
|
||||||
|
|
@ -176,34 +190,9 @@ def test_example(
|
||||||
proc.kill()
|
proc.kill()
|
||||||
err = e.stderr
|
err = e.stderr
|
||||||
|
|
||||||
errmsg: str = err.decode() if err else ''
|
|
||||||
|
|
||||||
# XXX, ALWAYS surface the subproc's full stderr
|
|
||||||
# whenever it exits non-zero!
|
|
||||||
#
|
|
||||||
# The prior impl only raised when the LAST stderr
|
|
||||||
# line contained 'Error', swallowing any crash whose
|
|
||||||
# traceback ends in a non-`XxxError:` line; in
|
|
||||||
# particular EVERY `tractor` root-actor crash ends
|
|
||||||
# with the strict-EG collapse note,
|
|
||||||
# '( ^^^ this exc was collapsed from a group ^^^ )',
|
|
||||||
# so ALL such failures were reduced to a bare
|
|
||||||
# `assert 1 == 0` in CI logs.. see GH #473.
|
|
||||||
rc: int|None = proc.returncode
|
|
||||||
if rc:
|
|
||||||
outmsg: str = out.decode() if out else ''
|
|
||||||
raise Exception(
|
|
||||||
f'Example script exited with rc={rc} !?\n'
|
|
||||||
f'\n'
|
|
||||||
f'stdout:\n'
|
|
||||||
f'{outmsg}\n'
|
|
||||||
f'\n'
|
|
||||||
f'stderr:\n'
|
|
||||||
f'{errmsg}\n'
|
|
||||||
)
|
|
||||||
|
|
||||||
# if we get some gnarly output let's aggregate and raise
|
# if we get some gnarly output let's aggregate and raise
|
||||||
if errmsg:
|
if err:
|
||||||
|
errmsg = err.decode()
|
||||||
errlines = errmsg.splitlines()
|
errlines = errmsg.splitlines()
|
||||||
last_error = errlines[-1]
|
last_error = errlines[-1]
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -301,16 +301,7 @@ async def start_listener(
|
||||||
f'>{{\n'
|
f'>{{\n'
|
||||||
f'|_{bs!r}\n'
|
f'|_{bs!r}\n'
|
||||||
)
|
)
|
||||||
bs.mkdir(
|
bs.mkdir()
|
||||||
# ensure the full ancestor tree for any nested
|
|
||||||
# (custom `filedir`) bindspace; the default
|
|
||||||
# `get_rt_dir()` space is pre-created but a custom
|
|
||||||
# one may have missing parents.
|
|
||||||
parents=True,
|
|
||||||
# avoid `FileExistsError` from racing actors, same
|
|
||||||
# guard as in `get_rt_dir()`.
|
|
||||||
exist_ok=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
with _reraise_as_connerr(
|
with _reraise_as_connerr(
|
||||||
src_excs=(
|
src_excs=(
|
||||||
|
|
@ -563,18 +554,11 @@ class MsgpackUDSStream(MsgpackTransport):
|
||||||
]:
|
]:
|
||||||
sock: trio.socket.socket = stream.socket
|
sock: trio.socket.socket = stream.socket
|
||||||
|
|
||||||
# NOTE, the `bytes` case is a linux-only artifact: setting
|
# NOTE XXX, it's unclear why one or the other ends up being
|
||||||
# `SO_PASSCRED` (see `open_unix_socket_w_passcred()`)
|
# `bytes` versus the socket-file-path, i presume it's
|
||||||
# causes the kernel to *autobind* the un-named client
|
# something to do with who is the server (called `.listen()`)?
|
||||||
# sock to an abstract-namespace addr which python
|
# maybe could be better implemented using another info-query
|
||||||
# delivers as `bytes`; the listener-bound end is always
|
# on the socket like,
|
||||||
# the fs-path `str`. On platforms WITHOUT autobind
|
|
||||||
# (macOS et al) the un-bound end instead reports as an
|
|
||||||
# empty `str` so BOTH names arrive as `str`s and the
|
|
||||||
# real fs-path is whichever is non-empty: `peername` on
|
|
||||||
# the connect side, `sockname` on the accept side.
|
|
||||||
#
|
|
||||||
# for socket-api deats see,
|
|
||||||
# https://beej.us/guide/bgnet/html/split-wide/system-calls-or-bust.html#gethostnamewho-am-i
|
# https://beej.us/guide/bgnet/html/split-wide/system-calls-or-bust.html#gethostnamewho-am-i
|
||||||
sockname: str|bytes = sock.getsockname()
|
sockname: str|bytes = sock.getsockname()
|
||||||
# https://beej.us/guide/bgnet/html/split-wide/system-calls-or-bust.html#getpeernamewho-are-you
|
# https://beej.us/guide/bgnet/html/split-wide/system-calls-or-bust.html#getpeernamewho-are-you
|
||||||
|
|
@ -586,24 +570,8 @@ class MsgpackUDSStream(MsgpackTransport):
|
||||||
case (bytes(), str()):
|
case (bytes(), str()):
|
||||||
sock_path: Path = Path(sockname)
|
sock_path: Path = Path(sockname)
|
||||||
|
|
||||||
# XXX, no-autobind case (macOS): the un-bound end
|
case (str(), str()): # XXX, likely macOS
|
||||||
# is `''`, NOT a `bytes` abstract-ns addr; taking
|
sock_path: Path = Path(peername)
|
||||||
# `peername` unconditionally (as prior impl did)
|
|
||||||
# delivers garbage `Path('')` addrs on the accept
|
|
||||||
# side!
|
|
||||||
case (str(), str()):
|
|
||||||
bound_name: str = (
|
|
||||||
peername
|
|
||||||
or
|
|
||||||
sockname
|
|
||||||
)
|
|
||||||
if not bound_name:
|
|
||||||
raise ValueError(
|
|
||||||
f'Empty UDS (peername, sockname) pair ??\n'
|
|
||||||
f'peername: {peername!r}\n'
|
|
||||||
f'sockname: {sockname!r}\n'
|
|
||||||
)
|
|
||||||
sock_path: Path = Path(bound_name)
|
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue