Compare commits

..

4 Commits

Author SHA1 Message Date
Gud Boi b64c89f05a Contain `@context` final-result disconnects
A caller can depart after its endpoint finishes but before the final
`Return` reaches it. Letting that `TransportClosed` escape `_invoke()`
cancels unrelated RPCs in the actor's shared service nursery.

Guard only the final result send and report the actor, context, scope,
channel and transport error using existing primitive renderers. Keep
endpoint execution and nursery teardown outside that suppression.

Extend the real-TCP reproducer with a genuine endpoint-raised
`TransportClosed` identity control. Snapshot the endpoint, sibling and
nursery owner through the shared task formatter when cancellation
exposes a collateral failure.

Prompt-IO: ai/prompt-io/20261019T145431Z_0553fcf8_ctx_return_fix_prompt_io.md
Prompt-IO: ai/prompt-io/20261020T193619Z_0553fcf8_review_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-21 14:23:33 -04:00
Gud Boi bf2bb1c9a6 Mv task UID formatting into `devx.pformat`
Add `pformat_task()` beside `pformat_task_uid()` so diagnostics can
inspect an explicit endpoint, sibling or nursery-owning `trio.Task`.
Keep the current-task default and decimal head/tail identity format.

Preserve the `tractor.log` entry point with lazy forwarding, avoiding
eager debugger initialization during logging imports. Move the
existing formatting TODOs with the implementation.

Exercise real parent/child tasks without an actor, including coroutine
identity, nursery ownership and equivalent log/tooling UID output.

Prompt-IO: ai/prompt-io/20261021T145344Z_0553fcf8_task_format_prompt_io.md
Prompt-IO: ai/prompt-io/20261021T151133Z_0553fcf8_uid_move_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-21 11:29:32 -04:00
Gud Boi 0553fcf88c Reproduce `@context` final-`Return` disconnect cascade
A chart disconnect can leave its context endpoint returning over
a closed channel. Exercise the real `_rpc._invoke()` dispatcher
with loopback TCP and a sibling service to isolate the resulting
`TransportClosed` nursery cancellation.

Deats,
- Order caller close, callee EOF and endpoint completion with
  events instead of timing sleeps or injected transport errors.
- Keep an ordinary-RPC control for existing response hardening.
- Capture the collateral task, actor/PID, nursery, cancel scope
  and cancellation source in a test-generated exception note.
- Record the live IB diagnosis and generated-test provenance.

The ordinary-RPC control passes; the context case remains red
with `ClosedResourceError` wrapped as `TransportClosed`. This
commit establishes the regression before runtime remediation.

Prompt-IO: ai/prompt-io/20261019T140536Z_9a0f220f_final_return_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-19 10:51:17 -04:00
Gud Boi 9a0f220fd5 Fix built-in level names in `at_least_level()`
The `CUSTOM_LEVELS`-only lookup raised `KeyError` for built-in
log levels such as 'INFO'. Resolve other registered names through
`logging.getLevelNamesMapping()` only when the custom lookup
misses, without eagerly evaluating the fallback on custom hits.

Deats,
- keep custom precedence, case-insensitive names and `NOTSET=0`;
- preserve integer inputs and unknown-name `KeyError` behavior;
- keep logger-local and global `isEnabledFor()` disable controls;
- advertise integer inputs on the adapter's `at_least_level()`;
- add 62 raw/adapted regressions with explicit threshold examples
  and fixture docs distinguishing instance and global log state.

The logging suite passed all 70 cases; the original Piker consumer
probe passed 8/8 with its string-level feed guard retained.

Prompt-IO: ai/prompt-io/20261011T053737Z_a966928d_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-16 20:31:49 -04:00
19 changed files with 1395 additions and 20 deletions

View File

@ -0,0 +1,142 @@
---
created_at: 2026-10-11T05:37:37Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: wkt/address_logging_levels
scope: code
substantive: true
raw_file: 20261011T053737Z_a966928d_prompt_io.raw.md
---
## Prompt
The user supplied this original request:
> maybe we can add a commit to tractor on top of addr_unpacking,
> also scan for any other downstream from main logging history and
> cherry it there and then add your fix, and finally move piker_pin
> to that? rather not hackaround bug alreadypinningnonmain
The current instruction narrowed execution to a managed Tractor
worktree, `wkt/address_logging_levels`, starting at the verified
`addr_unpacking` OID a966928dead4cbf369ad244834ce9dbe988ac8ca.
Scan local logging history, preserve relevant originals where
possible, implement the standard-name fix with meaningful tests,
and verify Piker's original cache-hit regression through an import
overlay. Leave changes uncommitted for human review and leave
`piker_pin`, Piker's workaround, environments, and lockfiles intact.
## Response summary
Created the managed worktree and recorded approved local discovery
in Git administrative storage. No equivalent name-resolution fix
or additional prerequisite was found. Patch-ID comparisons of
`tractor/log.py` and `tests/test_log_sys.py` identified these
already-landed equivalents:
- d653cd2c -> a2c8af55: fast caller lookup.
- 4f811a87 -> d1d3fc58: lazy optional imports.
- 361f6fcc and other copies -> bb0b2ffa: custom-level factory.
- 11b9a870 and other copies -> b6bf865f: nested logger names.
- 74784780 and other copies -> 8bb9df9e: logging-spec API.
The base also includes 88cc68ed (custom-level re-registration),
67280c28 (disable-aware guards), and 089e158d (dynamic caller
fallback). The non-equivalent 53e10861 thread-header change is
unrelated and was excluded. No cherry-pick was needed.
The source fix uses public stdlib name resolution after Tractor's
custom-level lookup. It keeps the existing `KeyError` contract
instead of introducing a `ValueError` compatibility change.
## Files changed
- `tractor/log.py`: standard/application name resolution and
integer-capable adapter annotation.
- `tests/test_log_sys.py`: 62 raw/adapted regression cases with
detailed failure-mechanism documentation and isolated log state.
- `ai/prompt-io/README.md`: shared provenance policy.
- This structured record and its immutable raw partner.
## Verification
Run from the new Tractor worktree:
```xsh
env PYTHONPATH=/home/goodboy/repos/tractor/wkts/address_logging_levels /home/goodboy/repos/tractor/py313/bin/python -m pytest tests/test_log_sys.py -k at_least_level
```
Before the source fix: 30 failed, 32 passed, 8 deselected. Failures
were name-lookup `KeyError`s, including INFO and NOTSET.
```xsh
env PYTHONPATH=/home/goodboy/repos/tractor/wkts/address_logging_levels /home/goodboy/repos/tractor/py313/bin/python -m pytest tests/test_log_sys.py
/nix/store/qmrz7inmmlfmx1nghix8ymghh5yd9gnv-ruff-0.15.22/bin/ruff check tractor/log.py tests/test_log_sys.py
git diff --check
```
After the fix: 70 passed; Ruff and whitespace checks passed.
Tractor tests used CPython 3.13.11 and pytest 9.1.0. Source imports
were asserted beneath the new worktree. The inherited VIRTUAL_ENV
named Modden, but the explicit Tractor interpreter was used;
TRACTOR_SPAWN_METHOD and TRACTOR_LOGLEVEL were unset.
The existing `modern_consumer_probe.py` was run through an isolated
wrapper with the new Tractor source and Piker's existing
`land_flake_update/py313` interpreter (CPython 3.13.14). Original
Piker Git-tree modules and tests were overlaid only in memory;
the `--standard-level-fix` option was absent. All 8 cases passed,
including both cache-miss and cache-hit variants from b691422b.
The installed Tractor distribution metadata still identifies the
base OID; import assertions independently verified that executable
Tractor and its logging module came from the new source worktree.
Direct Piker imports first failed for missing native libraries
(libstdc++, then libzstd). A process-local LD_LIBRARY_PATH containing
already-present library outputs resolved from Piker's locked Nix
input fixed those imports. No shell hook, dependency installation,
environment synchronization, live broker, or actor suite was run.
## Human edits
The human directed the upstream Tractor fix instead of a Piker
workaround, selected the exact stack base and local discovery scope,
required compatibility and consumer verification, and reserved
commits and ref publication for review. These are human-directed
agent changes; no subsequent manual edits or acceptance are recorded.
Subsequent local Tuicr review supplied 17 human-selected comments in
`address_logging_levels@wkt-address_logging_levels/staged/a966928`.
The reviewer directed explicit INFO(20) fixture documentation,
boolean selector meanings, the `is_info_level_enabled` callable
name, threshold comments at numeric values, and precise API and
failure descriptions instead of vague terms or Piker-specific
context. The agent applied those directions and replaced the custom
level membership/subscript pair with `CUSTOM_LEVELS.get(name)` plus
a lazy stdlib fallback on `None`, preserving zero and `KeyError`.
The registered-level regression now uses 35, between WARNING(30)
and ERROR(40), and tests it at INFO(20) then ERROR(40).
After these review adjustments, all 70 logging tests passed with
the same source-verified Tractor CPython 3.13.11 environment.
Targeted Ruff and `git diff --check` also passed. The staged index
was preserved; review adjustments remain unstaged for human review.
The original `.raw.md` watermark and response capture are unchanged.
A second review selected three follow-up comments about the logger
registration hook, fixture parameter meaning, and the INFO-specific
query name after changing the logger's threshold to ERROR. The
fixture now distinguishes instance-local level/disabled settings
from temporary registration and the process-global disable cutoff,
and includes a short consumer example. Each fixture case allocates
a fresh raw logger; only the query form varies between the module
function and an adapter method wrapping that logger. The test which
changes the threshold now names its query `is_level_enabled`.
All 70 logging tests passed again with source imports verified in
this worktree under Tractor's CPython 3.13.11 interpreter. Targeted
Ruff and whitespace checks passed. These follow-up adjustments are
unstaged; the current human-staged baseline and original raw prompt
record are preserved.

View File

@ -0,0 +1,34 @@
---
created_at: 2026-10-11T05:37:37Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: wkt/address_logging_levels
diff_cmd: git diff a966928dead4cbf369ad244834ce9dbe988ac8ca wkt/address_logging_levels
---
> `git diff a966928dead4cbf369ad244834ce9dbe988ac8ca wkt/address_logging_levels -- tractor/log.py`
Extend `at_least_level()` to resolve uppercase names first through
`CUSTOM_LEVELS`, then through `logging.getLevelNamesMapping()`.
Preserve integer inputs, zero-valued NOTSET, `KeyError` for unknown
names, and `Logger.isEnabledFor()` disable semantics. Update the
adapter's annotation to advertise its existing integer support.
> `git diff a966928dead4cbf369ad244834ce9dbe988ac8ca wkt/address_logging_levels -- tests/test_log_sys.py`
Add an isolated raw/adapted logger fixture and regressions for
standard names and aliases, lowercase names, custom CANCEL,
integer and NOTSET inputs, application-registered names, unknown
names, and logger-local/global disable controls.
Tractor's logging suite passes: **70 tests**. Before the source fix,
the new regressions produced **30 failures**, including `INFO`,
`NOTSET`, and application-registered names. Ruff and whitespace
checks also pass.
The original Piker consumer probe now passes **8/8**, including the
cache-hit case with `log.at_least_level('info')` retained. Import
assertions confirm it used the new Tractor worktree. The probe needed
a process-local overlay of existing Nix libraries to import Piker.

View File

@ -0,0 +1,71 @@
---
created_at: 2026-10-19T14:05:36Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 9a0f220f
scope: tests
substantive: true
raw_file: 20261019T140536Z_9a0f220f_final_return_prompt_io.raw.md
---
## Prompt
The human reported chart Ctrl-C causing search's final Return delivery
to raise TransportClosed in datad.ib, cancelling data services and then
pikerd's trading/sampler services. They asked whether the Piker branch
ib_connectivity_harden already remediates it and authorized a minimal
Tractor sibling-survival reproducer if no existing remediation applies:
> ok let's do it if we find no other redmediations per above.
The human explicitly selected capture of this test-only provenance.
## Response summary
Compared ib_connectivity_harden with the committed evaluation baseline:
efa72980's IB socket-disconnect propagation is already present; the
missing d126302f/c0dae89a changes concern idle arithmetic and sampler
cancellation-state access. Tractor ebf2258b hardens ordinary RPC response
sends, but the context final Return remains exposed. Inspected the
additional ipc_fault_regressions branch: its EOF/context-status changes
do not guard this final-result send.
Added a focused real-TCP, same-process dispatcher regression with an
ordinary RPC control and a context case. Explicit peer-close/EOF events
force the failure ordering. A sibling request/response checks service
survival; the failure path confirms premature sibling cancellation.
## Files changed
- tests/test_context_final_return_disconnect.py: reproducer and control.
- This provenance pair.
## Verification
- Ordinary RPC control passed.
- Context case failed with the reported TransportClosed caused by
ClosedResourceError, confirming collateral nursery cancellation.
- Final two-case run: 1 failed, 1 passed in 0.07s.
- Ruff passed; git diff --check passed.
- Test uses isolated local TCP, no broker, feed, Qt or asyncio bridge.
- Root actor and channels exit through their context managers.
- No production fix, staging, commit or ref changes.
## Human edits
None observed in the generated test at capture time.
## Follow-up: identify the collateral sibling
The human requested the task repr and runtime metadata behind the
test-generated exception note. The cancellation handler now captures
the sibling task, Actor.aid (including PID), parent nursery, nursery
cancel scope and original trio.Cancelled repr while still unwinding.
The test attaches that snapshot to the escaping TransportClosed;
Tractor production reporting and exception handling are unchanged.
The updated two-case reproducer again yielded 1 passed, 1 failed in
0.07s, with the sibling identity and source invoke task in the note.
Ruff and git diff --check passed after the diagnostic change.

View File

@ -0,0 +1,66 @@
---
created_at: 2026-10-19T14:05:36Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 9a0f220f
diff_cmd: git diff --no-index /dev/null tests/test_context_final_return_disconnect.py
---
## Captured output
`ib_connectivity_harden` has no fix for this path. Its IB socket
disconnect handling is already in our evaluation; the two missing
commits fix idle-timer arithmetic and sampler cancellation-state
access.
I also checked Tractor’s related fault work. The existing
response-send fix covers ordinary RPCs, but leaves the `@context`
final-result send exposed. I’m building the reproducer in the
evaluated Tractor worktree, with isolated local IPC.
The reproducer hits the same failure with real loopback TCP:
**ordinary RPC passes; `@context` fails**, and the unrelated
sibling is confirmed cancelled before it can answer a request.
The two cases finish in 0.07 seconds, without IB, Qt or asyncio.
## Generated test
> `git diff --no-index /dev/null tests/test_context_final_return_disconnect.py`
The file is currently untracked. The pointer above displays the entire
generated artifact without duplicating it in this record.
finish_after_disconnect() is a real Tractor context endpoint that
publishes readiness and returns after an event. ordinary_result()
provides a non-context RPC control. The parameterized test invokes the
actual dispatcher with a real root actor and real loopback TCP channels.
It drains startup messages, closes the caller, observes callee EOF,
closes the callee channel, then releases the endpoint. A nursery sibling
must answer a subsequent request. On the observed TransportClosed it
also verifies collateral sibling cancellation and re-raises the fault.
The explicit event ordering isolates final-result delivery, without
mocked exceptions, sleeps, provider connections or process supervision.
## Final verification
Command environment: evaluation Piker py313/bin/python (Python 3.13.14,
pytest 9.0.2), importing Tractor from address_logging_levels at 9a0f220f.
VIRTUAL_ENV was removed; TRACTOR_SPAWN_METHOD and TRACTOR_LOGLEVEL unset.
LD_LIBRARY_PATH included the existing Nix GCC runtime library.
`python -m pytest tests/test_context_final_return_disconnect.py --tpt-proto=tcp --tb=short`
Result: 1 failed, 1 passed in 0.07s. Diagnostic --tb=short was used
after the ordinary full-trace run confirmed the dispatcher failure.
The context case raises TransportClosed caused by ClosedResourceError
at MsgpackTransport.send(), with the note:
The unrelated sibling was cancelled before it could answer the post-disconnect request.
`ruff check tests/test_context_final_return_disconnect.py`
All checks passed!
`git diff --check` passed. No production runtime edits were made.

View File

@ -0,0 +1,61 @@
---
created_at: 2026-10-19T14:54:31Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
scope: code
substantive: true
raw_file: 20261019T145431Z_0553fcf8_ctx_return_fix_prompt_io.raw.md
---
## Prompt
After committing the isolated regression on
wkt/ctx_final_return_disconnect, the human asked:
> committed, ok now a patch?
Earlier guidance requested runtime diagnostic information at the
production detection point, alongside the test's sibling metadata.
## Response summary
Added a narrow final-result delivery guard in _rpc._invoke(). A
TransportClosed while sending a successful context endpoint's final
Return now produces a metadata-rich warning rather than cancelling
the actor's shared service nursery. Actual endpoint transport errors
and cancellation retain the existing propagation path.
Extended the reproducer with a genuine endpoint-raised TransportClosed
control and an identity assertion, preventing broad suppression of
application failures. The formerly red context delivery case passes.
## Files changed
- tractor/runtime/_rpc.py: guard final response delivery and report
actor/task/context/nursery/channel metadata on peer disconnect.
- tests/test_context_final_return_disconnect.py: endpoint-error control.
- This provenance pair.
## Verification
Ran from the Tractor worktree at committed base 0553fcf8 using
/home/goodboy/repos/piker/wkts/ib_downstream_pin_eval/py313/bin/python.
VIRTUAL_ENV was removed and LD_LIBRARY_PATH included the existing
Nix GCC runtime library; tests select isolated TCP addresses.
- pytest tests/test_context_final_return_disconnect.py tests/test_rpc.py
--tpt-proto=tcp: 10 passed in 0.93s.
- pytest tests/test_context_stream_semantics.py --tpt-proto=tcp:
65 passed in 59.80s.
- ruff check tractor/runtime/_rpc.py
tests/test_context_final_return_disconnect.py: passed.
- git diff --check: passed.
- No live broker/chart trial was run by the agent.
- All patch changes remain unstaged; no commit or ref move.
## Human edits
None observed in this patch at capture time.

View File

@ -0,0 +1,42 @@
---
created_at: 2026-10-19T14:54:31Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
diff_cmd: git diff HEAD -- tractor/runtime/_rpc.py tests/test_context_final_return_disconnect.py
---
## Captured response
I’ll patch the context final-result delivery path, then run the
reproducer and the related RPC/context tests.
## Generated patch
> `git diff HEAD -- tractor/runtime/_rpc.py`
Guard only chan.send(return_msg) after the context endpoint returns.
Catch normalized TransportClosed and warn with actor identity, task,
RPC/context ID, nursery, cancel scope, channel and transport error.
Keep endpoint execution and nursery-exit errors outside the guard.
> `git diff HEAD -- tests/test_context_final_return_disconnect.py`
Extend the real-TCP reproducer with an endpoint-raised TransportClosed
control. Verify its identity survives propagation and that swallowing
it would fail the test. Preserve the successful ordinary/context
cases' sibling responsiveness and RPC task cleanup assertions.
## Captured verification response
The patch turns the reproducer green. It catches
`TransportClosed` only around the final-result send and logs the
actor, task, RPC/context ID, nursery, cancel scope and channel.
An added control confirms that an endpoint’s own `TransportClosed`
still propagates with its original identity.
Checks passed: **10 RPC/reproducer cases + 65 context/stream
cases**, plus Ruff and whitespace checks.

View File

@ -0,0 +1,64 @@
---
created_at: 2026-10-20T19:36:19Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
scope: code
substantive: true
raw_file: 20261020T193619Z_0553fcf8_review_prompt_io.raw.md
---
## Prompt
Address three selected local Tuicr comments from
`address_logging_levels@wkt-ctx_final_return_disconnect/unstaged/0553fcf`.
The human requested detailed endpoint/nursery-owner task diagnostics,
an extra newline after the post-disconnect request prose, and warning
output consistent with runtime primitive pformat conventions. The `-r`
invocation authorizes local responses only; staging and commits remain
human-controlled.
Selected parent comment IDs:
- `337f29ca-73b2-4351-b150-d0160d67a2dc`
- `2c522ded-3bf1-4827-af8f-c27822d7c48d`
- `f00171c0-7a10-455f-a9e3-dd85bfa63df8`
## Response summary
The existing pformat_task_uid helper only names the current task. Kept
detailed arbitrary-task formatting local to the regression for now, and
reused existing primitive formatters in the runtime's warning path.
Preserved the narrow final-send suppression boundary and genuine endpoint
error propagation control. Captured a selected-path pre-review Git tree
without changing the real index, plus original comment/review-state data.
## Files changed
- `tests/test_context_final_return_disconnect.py`: task snapshots and note.
- `tractor/runtime/_rpc.py`: structured primitive warning reports.
## Verification
See the raw partner for the 10-pass modern-stack check, successful Ruff
and whitespace checks, expected-red diagnostic probe, and the separately
reproduced pre-existing Trio 0.29 cancellation-provenance failure.
Neither existing environment meets every current harness version bound:
the modern downstream interpreter has pytest 9.0.2; the worktree has an
older Trio. No dependency setup or lock-file changes were authorized.
## Human edits
The captured pre-review work includes the runtime fix and endpoint-error
test control already under review. Existing comments, TODOs and review
markers were retained; this record describes only the new adjustments.
## Module-qualified call follow-up
After tooling commit `bf2bb1c9`, the human requested restoring the
existing `_pformat` module reference instead of a direct function import.
Removed that import and qualified the sibling, endpoint and owner task
calls, wrapping assignments to retain the source line-length convention.
The raw partner retains the original generation history. This formatting
correction changes no task-selection or disconnect behavior.

View File

@ -0,0 +1,47 @@
---
created_at: 2026-10-20T19:36:19Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
diff_cmd: git diff HEAD~1..HEAD
---
## Generated review adjustments
> `git diff HEAD~1..HEAD -- tests/test_context_final_return_disconnect.py`
Added a test-local arbitrary-task formatter and snapshots of the endpoint
task and its nursery-owning task before the dispatcher wrapper exits.
The collateral-cancellation report includes actor identity, sibling,
endpoint and owner task fields, parent cancel-scope state and the original
cancellation repr. Added the requested blank line after the request prose.
> `git diff HEAD~1..HEAD -- tractor/runtime/_rpc.py`
Replaced ad-hoc warning labels with Actor/Context/Channel pformat output,
the existing cancel-scope formatter and a nested transport-error report.
The TransportClosed guard still encloses only final-result transmission.
## Verification output
Modern downstream evaluation environment (Python 3.13.14, Trio 0.32.0,
pytest 9.0.2): regression plus RPC suite, 10 passed in 1.22s.
Standalone Ruff: All checks passed! Git whitespace check passed.
The worktree environment has Python 3.13.13, Trio 0.29.0 and pytest 9.1.0:
9 passed, 1 failed because the pre-existing test expects TransportClosed
in cancellation provenance, while this older Trio renders only Cancelled.
Executing the captured pre-review test in memory reproduced that assertion
failure; it was not caused by these review adjustments. No environment or
dependency was changed. The modern environment's pytest is below the
current harness reference's >=9.0.3 requirement; that limitation remains.
An in-memory probe removed only the final-send guard and reproduced the
expected TransportClosed failure. It verified the enhanced exception note
contains the blank separator, actor identity, three structured task
reports, task ownership fields and cancellation source. Source files were
unchanged by the probe. Initial scratch-probe validation errors were
corrected before the successful expected-red checks; they were not
repository test regressions.

View File

@ -0,0 +1,56 @@
---
created_at: 2026-10-21T14:53:44Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
scope: code
substantive: true
raw_file: 20261021T145344Z_0553fcf8_task_format_prompt_io.raw.md
---
## Prompt
The human requested moving the new regression-local Trio task formatter
into devx.pformat, scanning branches for earlier equivalents first, and
noted that log.pformat_task_uid already extracts the task ID. Follow-up:
the reviewed change was good enough apart from the function move; the
QA agent had already been alerted to the new order-bootstrap race.
## Response summary
Found no earlier detailed task formatter across 810 local refs and 18
worktrees. Extracted pformat_task into tooling and made the existing UID
helper accept an explicit task, preserving its established log output.
Added focused coverage that inspects a real child from its parent and
updated the cancellation regression to use the shared renderer.
## Files changed
- `tractor/devx/pformat.py`: reusable pformat_task.
- `tractor/log.py`: optional explicit task for pformat_task_uid.
- `tests/devx/test_pformat.py`: real task-selection/ownership coverage.
- `tests/test_context_final_return_disconnect.py`: shared-tool callers.
## Review boundary and verification
Pre-move tree: `27e75d8b1cdfec1d96284d993e55ad3c51c3804c`.
HEAD remains `0553fcf88cd37b4d487eafb992071d4d1cc6e06f`.
The real index was unchanged at extraction start. Existing final-send
work and provenance are retained, including the earlier review record
that documents the then-local helper. Original Tuicr replies were not
rewritten; this extraction supersedes their local-helper description.
See the raw partner for environment details and focused verification.
Final verification repeated after import/line-wrap adjustments: 75
formatter/logger tests and 10 final-Return/RPC tests passed; Ruff,
whitespace and added-source 69-column checks passed. A read-only Tuicr
comparison found the saved session now contains only the first original
comment/reply pair, unlike the earlier three-pair receipt. This extraction
did not write session state or recreate the absent comments.
## Human edits
This extraction follows the human's requested final placement. Existing
logging TODOs, pre-review changes and task/review states are preserved.

View File

@ -0,0 +1,49 @@
---
created_at: 2026-10-21T14:53:44Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
diff_cmd: git diff HEAD~1..HEAD
---
## Generated output
> `git diff HEAD~1..HEAD -- tractor/devx/pformat.py tractor/log.py`
Moved the regression-local task renderer into devx.pformat.pformat_task.
It accepts an explicit Trio task or defaults to the current task, and
reports identity, coroutine, parent nursery/owner and child nurseries.
The new renderer reuses log.pformat_task_uid for the short identity,
rather than independently extracting and slicing id(task).
Added a keyword-only optional task argument to that existing UID helper;
its current-task default, positional id_part and decimal head/tail output
remain unchanged. The full Task repr retains the full native identity.
Kept the UID import local to the richer renderer so log headers do not
gain a dependency on devx initialization. Existing UID/color TODOs remain.
> `git diff HEAD~1..HEAD -- tests/test_context_final_return_disconnect.py tests/devx/test_pformat.py`
Removed the local formatter and switched its three consumers to the
shared tooling function. Added an actor-free live parent/child task test
that checks explicit-task selection, default current-task selection,
nursery ownership, coroutine identity, and unchanged head/tail log IDs.
The accepted final-result send guard is untouched by this extraction.
## Existing-work discovery
Read-only scan: 810 local branch/remote-tracking refs, 18 worktree path
inventories and task-formatting definitions, plus relevant all-ref history.
Only log.pformat_task_uid and this session's regression helper were found.
The dirty pformat.py in wkt/tipc_backend_wg_stack contains an unrelated
comment spelling correction; no other worktree was modified.
## Verification
Worktree Python 3.13.13 / pytest 9.1.0 / Trio 0.29.0: formatter and logger
suites, 75 passed. Downstream Python 3.13.14 / pytest 9.0.2 / Trio 0.32.0:
final-Return regression and RPC suite, 10 passed. The older pytest in the
downstream environment remains below the current harness reference's
version bound; neither environment was changed. Ruff passed. A source
line-length check prompted wrapping before final verification.

View File

@ -0,0 +1,41 @@
---
created_at: 2026-10-21T15:11:33Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
scope: code
substantive: true
raw_file: 20261021T151133Z_0553fcf8_uid_move_prompt_io.raw.md
---
## Prompt
The human proposed moving pformat_task_uid into devx.pformat with the
new pformat_task and its unit test in a precursor commit, then placing
the final-result disconnect fix and matching tests in the next commit.
The request authorizes implementation and commit planning, not commits.
## Response summary
Moved the identity implementation into the formatting module, removed
the detailed formatter's reverse logging dependency, and preserved the
existing logging entry point through lazy forwarding. Retained the
existing TODO group and task-state markers. Extended focused identity
tests, verified the relocation and selected two dependency-ordered
whole-file commit boundaries with separate provenance.
## Files changed
- `tractor/devx/pformat.py`: canonical short and detailed task formatters.
- `tractor/log.py`: lazy forwarding entry point for existing log users.
- `tests/devx/test_pformat.py`: canonical/log entry-point equivalence.
## Verification and human edits
75 formatter/logger tests and 10 downstream final-Return/RPC tests passed.
Ruff and whitespace checks passed. Both existing environments are kept;
the downstream pytest-version caveat is disclosed in prior records.
Earlier raw prompt records are retained unchanged as generation history.
No staging, commits, history changes or Tuicr mutations were performed.

View File

@ -0,0 +1,33 @@
---
created_at: 2026-10-21T15:11:33Z
generated_by:
harness: opencode
provider: openai
model: openai/gpt-6.1-sol
git_ref: 0553fcf8
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- tractor/devx/pformat.py tractor/log.py tests/devx/test_pformat.py`
Moved pformat_task_uid's implementation into devx.pformat alongside
pformat_task. The detailed renderer now calls the UID renderer directly;
it no longer imports logging. Kept the tractor.log entry point as a
lazy forwarding function to avoid eagerly initializing devx/debugger
modules while logging itself is being imported. Preserved the existing
current-task default and decimal head/tail ID output, plus explicit-task
selection added in the preceding extraction. Moved the existing TODO
group verbatim and did not change its checklist markers.
Extended the task test to compare the logging entry point and canonical
tooling renderer for current and selected child tasks in both ID modes.
The existing tests/devx/test_pformat.py module is reused.
Verification: formatter/logger suites 75 passed in 0.16s on the worktree
environment; final-Return/RPC suites 10 passed in 1.76s on the downstream
evaluation environment. Ruff and git diff --check passed. The downstream
pytest version remains 9.0.2; its current harness-version gap is recorded
in the earlier review/extraction provenance. No environment was changed.
Prepared the requested tooling-first, disconnect-fix-second boundary
selection. Planning itself does not execute checks, stage or commit.

View File

@ -0,0 +1,24 @@
# AI Prompt I/O Log
This directory tracks prompt inputs and model outputs for
AI-assisted development across harnesses.
## Policy
Prompt logging follows the [NLNet generative AI policy][nlnet-ai].
All substantive AI contributions are logged with:
- Original harness, and provider/model when known
- Timestamps
- The prompts that produced the output
- Unedited model output (`.raw.md` files)
[nlnet-ai]: https://nlnet.nl/foundation/policies/generativeAI/
## Usage
Entries are created by the `/prompt-io` skill or automatically via
`/commit-msg` integration.
Human contributors remain accountable for all code decisions.
AI-generated content is never presented as human-authored work.

View File

@ -5,15 +5,82 @@ Unit tests for the `tractor.devx.pformat` render helpers.
from __future__ import annotations
import pytest
import trio
from tractor._exceptions import _mk_send_mte
from tractor.log import pformat_task_uid as log_task_uid
from tractor.devx.pformat import (
pformat_boxed_tb,
pformat_caller_frame,
pformat_task,
pformat_task_uid,
)
from tractor.msg._codec import _def_tractor_codec
def test_pformat_task_selects_another_tasks_identity() -> None:
'''
Describe the selected task rather than the diagnostic caller.
The final-Return regression needs to snapshot an endpoint and its
nursery owner from another task. Hold a real child in an active
nursery and inspect it from its parent, checking both identities
and public nursery relationships without requiring an actor.
Preserve the log header's decimal head/tail ID convention while
allowing that existing helper to select an explicit task too.
'''
async def main() -> None:
'''
Keep the inspected child alive through all report assertions.
'''
async def child(
task_status: trio.TaskStatus[trio.lowlevel.Task],
) -> None:
'''
Publish the real task before waiting for cancellation.
'''
task_status.started(trio.lowlevel.current_task())
await trio.sleep_forever()
owner: trio.lowlevel.Task = trio.lowlevel.current_task()
owner_id: str = str(id(owner))
assert pformat_task_uid() == f'{owner.name}[{owner_id[-6:]}]'
assert pformat_task_uid('head') == (
f'{owner.name}[{owner_id[:6]}]'
)
assert log_task_uid() == pformat_task_uid()
assert log_task_uid('head') == pformat_task_uid('head')
async with trio.open_nursery() as nursery:
task: trio.lowlevel.Task = await nursery.start(child)
task_id: str = str(id(task))
task_uid: str = f'{task.name}[{task_id[-6:]}]'
report: str = pformat_task(task)
assert pformat_task_uid(task=task) == task_uid
assert pformat_task_uid('head', task=task) == (
f'{task.name}[{task_id[:6]}]'
)
assert log_task_uid(task=task) == task_uid
assert log_task_uid('head', task=task) == (
pformat_task_uid('head', task=task)
)
assert f'uid={task_uid!r}' in report
assert f'task={task!r}' in report
assert f'coro={task.coro!r}' in report
assert f'parent_nursery: {nursery!r}' in report
assert f'parent_task={owner!r}' in report
assert 'child_nurseries: ()' in report
assert f'child_nurseries: ({nursery!r},)' in (
pformat_task(owner)
)
assert pformat_task() == pformat_task(owner)
nursery.cancel_scope.cancel()
trio.run(main)
@pytest.mark.parametrize(
'box_tb',
[True, False],

View File

@ -0,0 +1,273 @@
'''
Isolate final-result delivery from sibling service lifetimes.
'''
from uuid import uuid4
import msgspec
import pytest
import tractor
import trio
from tractor.ipc import Channel
from tractor.devx import pformat as _pformat
from tractor.msg import Aid, StartAck, Started
from tractor.runtime import _rpc
@tractor.context
async def finish_after_disconnect(
ctx: tractor.Context,
peer_closed: trio.Event,
endpoint_finished: trio.Event,
endpoint_error: tractor.TransportClosed|None = None,
) -> str:
'''
Return or raise the supplied error after the caller disconnects.
'''
await ctx.started('ready')
await peer_closed.wait()
endpoint_finished.set()
if endpoint_error is not None:
raise endpoint_error
return 'finished'
async def ordinary_result(
peer_closed: trio.Event,
endpoint_finished: trio.Event,
) -> str:
'''
Exercise the already-hardened ordinary RPC final-result path.
'''
await peer_closed.wait()
endpoint_finished.set()
return 'finished'
@pytest.mark.parametrize(
'context_endpoint, endpoint_transport_error',
[(False, False), (True, False), (True, True)],
ids=['ordinary-rpc', 'context', 'endpoint-transport-error'],
)
def test_final_return_disconnect_preserves_sibling(
reg_addr: tuple,
context_endpoint: bool,
endpoint_transport_error: bool,
) -> None:
'''
A departing caller must not cancel an unrelated service task.
Chart Ctrl-C made open_symbol_search finish before Tractor sent
its final Return. TransportClosed from that send escaped _invoke
and cancelled the shared service nursery, including IB feeds.
Use real loopback TCP channels and a real Actor/Context, invoking
_invoke directly to control the precise response-send ordering.
The caller consumes startup messages, closes its socket, and the
callee observes EOF and closes its channel before releasing the
successful endpoint. No transport exception is mocked or injected.
After _invoke completes, a sibling must still answer a request.
The ordinary-RPC case is a control for the existing response-send
hardening. The context case exposes the uncovered final Return.
Capture the sibling's task, actor, nursery and cancellation reprs
while unwinding so the failure identifies the collateral victim
and the cancellation source even after the runtime has exited.
Snapshot the endpoint task and its nursery's owning task before
they exit, retaining their coroutine and nursery ownership fields.
An endpoint-raised TransportClosed is a separate control: it
must retain its identity and current nursery propagation rather
than being suppressed as a final-result delivery failure.
This isolates response delivery; it does not exercise discovery,
process supervision, the asyncio bridge, or Qt cancellation.
'''
collateral_cancellations: list[tuple[trio.Cancelled, str]] = []
endpoint_error: tractor.TransportClosed|None = (
tractor.TransportClosed('endpoint transport failure')
if endpoint_transport_error else None
)
async def main() -> None:
'''
Order peer EOF before endpoint completion without sleeps.
'''
peer_closed = trio.Event()
endpoint_finished = trio.Event()
invoke_finished = trio.Event()
ping = trio.Event()
pong = trio.Event()
sibling_stopped = trio.Event()
endpoint_task_info: str = ''
endpoint_owner_info: str = ''
async def sibling(
task_status: trio.TaskStatus = trio.TASK_STATUS_IGNORED,
) -> None:
'''
Answer a post-disconnect request until explicitly stopped.
'''
task_status.started()
try:
await ping.wait()
pong.set()
await trio.sleep_forever()
except trio.Cancelled as exc:
if not pong.is_set():
assert endpoint_finished.is_set()
task: trio.lowlevel.Task = (
trio.lowlevel.current_task()
)
aid: Aid = actor.aid
nursery: trio.Nursery = task.parent_nursery
cancel_scope: trio.CancelScope = (
nursery.cancel_scope
)
sibling_info: str = _pformat.pformat_task(task)
scope_info: str = _pformat.pformat_cs(
cancel_scope,
var_name='services.cancel_scope',
)
runtime_info: str = (
f'Actor identity: {aid!r}\n'
f'\n'
f'Sibling task:\n{sibling_info}\n'
f'Endpoint task:\n{endpoint_task_info}\n'
f'Endpoint nursery owner:\n'
f'{endpoint_owner_info}\n'
f'{scope_info}\n'
f'Cancellation: {exc!r}\n'
)
collateral_cancellations.append(
(exc, runtime_info),
)
raise
finally:
sibling_stopped.set()
with trio.fail_after(10):
async with tractor.open_root_actor(
registry_addrs=[reg_addr],
enable_transports=['tcp'],
loglevel='error',
) as actor:
listeners = await trio.open_tcp_listeners(
0,
host='127.0.0.1',
)
[listener] = listeners
async with listener:
port: int = listener.socket.getsockname()[1]
caller_stream = await trio.open_tcp_stream(
'127.0.0.1',
port,
)
callee_stream = await listener.accept()
async with caller_stream, callee_stream:
caller = Channel.from_stream(caller_stream)
callee = Channel.from_stream(callee_stream)
caller._aid = actor.aid
callee._aid = Aid(
name='departing-chart',
uuid=str(uuid4()),
)
cid: str = str(uuid4())
endpoint = (
finish_after_disconnect
if context_endpoint else ordinary_result
)
kwargs = {
'peer_closed': peer_closed,
'endpoint_finished': endpoint_finished,
}
if context_endpoint:
kwargs['endpoint_error'] = endpoint_error
async def invoke(
task_status: trio.TaskStatus,
) -> None:
'''
Run the actual RPC dispatcher to completion.
'''
nonlocal endpoint_task_info
nonlocal endpoint_owner_info
try:
await _rpc._invoke(
actor,
cid,
callee,
endpoint,
kwargs,
task_status=task_status,
)
finally:
endpoint_task: trio.lowlevel.Task = (
trio.lowlevel.current_task()
)
endpoint_task_info = (
_pformat.pformat_task(endpoint_task)
)
endpoint_owner_info = (
_pformat.pformat_task(
services.parent_task,
)
)
invoke_finished.set()
async with trio.open_nursery() as services:
await services.start(sibling)
ctx = await services.start(invoke)
ack = await caller.recv()
assert isinstance(ack, StartAck)
assert ack.cid == cid
if context_endpoint:
started = await caller.recv()
assert isinstance(started, Started)
assert started.cid == cid
assert msgspec.msgpack.decode(
started.pld,
) == 'ready'
await caller.aclose()
with pytest.raises(tractor.TransportClosed):
await callee.recv()
await callee.aclose()
peer_closed.set()
await invoke_finished.wait()
assert endpoint_finished.is_set()
assert not sibling_stopped.is_set()
assert (callee, cid) not in actor._rpc_tasks
ping.set()
await pong.wait()
if context_endpoint:
assert ctx._result == 'finished'
services.cancel_scope.cancel()
try:
trio.run(main)
except tractor.TransportClosed as exc:
assert collateral_cancellations
cancelled, runtime_info = collateral_cancellations[0]
assert 'TransportClosed' in str(cancelled)
if endpoint_transport_error:
assert exc is endpoint_error
return
exc.add_note(
'The unrelated sibling was cancelled before it could '
'answer the post-disconnect request.\n'
f'\n'
f'{runtime_info}'
)
raise
assert not endpoint_transport_error, 'endpoint error suppressed'

View File

@ -2,7 +2,9 @@
`tractor.log`-wrapping unit tests.
'''
from collections.abc import Callable, Iterator
import importlib
from functools import partial
import logging
from pathlib import Path
import shutil
@ -274,6 +276,204 @@ def test_add_log_level_pluggable():
delattr(log.StackLevelAdapter, name.lower())
@pytest.fixture(params=[False, True], ids=['raw', 'adapter'])
def level_guard(
request: pytest.FixtureRequest,
monkeypatch: pytest.MonkeyPatch,
) -> Iterator[tuple[logging.Logger, Callable[[int|str], bool]]]:
'''
Return an INFO(20) logger and a log-level enablement query.
`is_info_level_enabled(level)` returns True when the requested
log level meets the logger's current threshold and logging is
not disabled. It queries that decision; it does not emit a msg.
The initial INFO threshold permits INFO(20), CANCEL(22) and
WARNING(30), but blocks DEBUG(10) and NOTSET(0). Tests may change
the logger's threshold or disable flag to query those settings.
Each case allocates a fresh raw `logging.Logger` as `std_log`;
consuming tests do not supply a logger. `request.param` selects
only the returned query:
- False (`raw`): bind `tractor.log.at_least_level` to `std_log`.
- True (`adapter`): wrap that same logger in `StackLevelAdapter`
and return its `at_least_level` method. The raw logger never
supplies an `at_least_level` method.
Register only `std_log` under its name in
`Logger.manager.loggerDict` so `Logger.setLevel()` and
`logging.disable()` can invalidate its enabled-level cache.
`std_log.setLevel(...)` and changes to `std_log.disabled` affect
that logger, not other loggers. The registration is temporary;
`logging.disable()` changes the process-global cutoff. Restore
that cutoff and the registration after each case.
A consumer can use either query form in the same way::
def test_info_query(level_guard):
std_log, is_info_level_enabled = level_guard
assert std_log.level == logging.INFO
assert is_info_level_enabled('INFO')
assert not is_info_level_enabled('DEBUG')
'''
std_log = logging.Logger('level_guard_test', logging.INFO)
monkeypatch.setitem(
logging.Logger.manager.loggerDict,
std_log.name,
std_log,
)
orig_disable: int = logging.root.manager.disable
# Clear inherited global suppression so INFO queries can pass.
# NOTSET(0) removes logging.disable()'s positive-level cutoff.
logging.disable(logging.NOTSET)
try:
is_info_level_enabled: Callable[[int|str], bool]
if request.param:
guarded_log = log.StackLevelAdapter(std_log, {})
is_info_level_enabled = guarded_log.at_least_level
else:
is_info_level_enabled = partial(
log.at_least_level, std_log,
)
yield std_log, is_info_level_enabled
finally:
logging.disable(orig_disable)
@pytest.mark.parametrize(
'level, enabled',
[
# String log levels are compared with the INFO(20) threshold.
('INFO', True),
('info', True),
('WARNING', True),
('warning', True),
('DEBUG', False),
('debug', False),
('ERROR', True),
('CRITICAL', True),
('WARN', True), # WARNING(30) alias, above INFO(20).
('FATAL', True), # CRITICAL(50) alias, above INFO(20).
('CANCEL', True), # Custom CANCEL(22), above INFO(20).
('cancel', True),
('TRANSPORT', False), # TRANSPORT(5), below INFO(20).
(logging.INFO, True), # 20 equals the INFO(20) threshold.
(logging.DEBUG, False), # 10 is below INFO(20).
(22, True), # CANCEL(22) is above INFO(20).
(0, False), # NOTSET(0) is below INFO(20).
('NOTSET', False),
('notset', False),
],
)
def test_at_least_level_names_and_integers(
level_guard,
level: int|str,
enabled: bool,
):
'''
String names of built-in log levels must not raise `KeyError`.
`tractor.log.at_least_level()` indexed `CUSTOM_LEVELS` for every
string query, so built-in logging levels such as 'INFO' raised
`KeyError` before testing whether logging was enabled. Query the
documented log levels and aliases, lowercase spellings, custom
levels and integer equivalents. Each result must match its
expected INFO(20) threshold comparison without changing that
threshold; NOTSET must resolve even though its value is zero.
See Python's `logging` docs for built-in level values:
https://docs.python.org/3/library/logging.html#logging-levels
'''
std_log, is_info_level_enabled = level_guard
assert is_info_level_enabled(level) is enabled
# A query must not reconfigure the INFO(20) logger.
assert std_log.level == logging.INFO
def test_at_least_level_registered_name(
level_guard,
monkeypatch: pytest.MonkeyPatch,
):
'''
Query a custom log level registered with `logging.addLevelName()`.
`tractor.log.at_least_level()` raised `KeyError` when a string
log level was absent from `CUSTOM_LEVELS`, even when registered
by `logging.addLevelName()`. Register APP_LEVEL_GUARD(35), between
WARNING(30) and ERROR(40). Its lowercase query must pass at
INFO(20), and its uppercase query must fail at ERROR(40). Restore
the logging registrations afterward so later tests do not inherit
this custom level.
'''
monkeypatch.setattr(
logging, '_nameToLevel', logging._nameToLevel.copy(),
)
monkeypatch.setattr(
logging, '_levelToName', logging._levelToName.copy(),
)
# Custom 35 is above INFO(20), but below ERROR(40).
logging.addLevelName(35, 'APP_LEVEL_GUARD')
std_log, is_level_enabled = level_guard
assert is_level_enabled('app_level_guard') is True
std_log.setLevel(logging.ERROR)
assert is_level_enabled('APP_LEVEL_GUARD') is False
@pytest.mark.parametrize('level', ['unknown_level_guard', '20', ''])
def test_at_least_level_unknown_name(
level_guard,
level: str,
):
'''
Reject unknown log-level names with an explicit `KeyError`.
The old `CUSTOM_LEVELS` lookup raised `KeyError` with the
uppercase query as its key. Log-level name lookup must not
silently enable misspelled levels or treat numeric strings as
integer inputs. Each invalid name must still raise `KeyError`
carrying its uppercase spelling, including an empty name.
'''
std_log, is_info_level_enabled = level_guard
with pytest.raises(KeyError) as excinfo:
is_info_level_enabled(level)
assert excinfo.value.args == (level.upper(),)
assert std_log.level == logging.INFO
@pytest.mark.parametrize('level', ['info', 'cancel', logging.INFO, 22])
@pytest.mark.parametrize('suppression', ['logger', 'global'])
def test_at_least_level_disable_controls(
level_guard,
level: int|str,
suppression: str,
):
'''
Log-level name resolution must honor logging disable controls.
Earlier guards checked only `Logger.getEffectiveLevel()` and
rendered payloads despite disable controls. Prime a positive
enabled-level result, then disable the logger or all logging.
Built-in INFO and custom CANCEL string names, plus their integer
equivalents, must return False even though the logger's effective
INFO(20) threshold remains unchanged. Query once before disabling
to ensure a cached True result cannot bypass suppression.
'''
std_log, is_info_level_enabled = level_guard
assert is_info_level_enabled(level) is True
if suppression == 'logger':
std_log.disabled = True
else:
logging.disable(logging.CRITICAL)
assert is_info_level_enabled(level) is False
assert std_log.getEffectiveLevel() == logging.INFO
@pytest.mark.parametrize(
'suppression',
[

View File

@ -25,7 +25,14 @@ import sys
import textwrap
import traceback
from trio import CancelScope
from trio import (
CancelScope,
Nursery,
)
from trio.lowlevel import (
Task,
current_task,
)
def add_div(
@ -219,6 +226,70 @@ def pformat_caller_frame(
return tb_str
# TODO IDEAs:
# -[ ] move to `.devx.pformat`?
# -[ ] do per task-name and actor-name color coding
# -[ ] unique color per task-id and actor-uuid
def pformat_task_uid(
id_part: str = 'tail',
*,
task: Task|None = None,
) -> str:
'''
Render a task's name and a decimal head/tail slice of its ID.
Default to the current task, or select an explicit `task` when
rendering another task's identity from diagnostic tooling.
'''
if task is None:
task = current_task()
tid: str = str(id(task))
if id_part == 'tail':
tid_part: str = tid[-6:]
else:
tid_part: str = tid[:6]
return f'{task.name}[{tid_part}]'
def pformat_task(
task: Task|None = None,
) -> str:
'''
Render a Trio task's identity, execution and nursery ownership.
Default to the current task. An explicit `task` lets a caller
inspect a sibling or nursery owner without selecting itself.
Capture the report while that task is alive to retain its active
`Task.parent_nursery` and `Task.child_nurseries` relationships.
'''
if task is None:
task = current_task()
task_uid: str = pformat_task_uid(task=task)
nursery: Nursery|None = task.parent_nursery
parent_task: Task|None = (
nursery.parent_task if nursery is not None else None
)
child_nurseries: tuple[Nursery, ...] = tuple(
task.child_nurseries,
)
return (
f'<Task(\n'
f' |_name: {task.name!r}\n'
f' uid={task_uid!r}\n'
f' task={task!r}\n'
f' coro={task.coro!r}\n'
f' |_parent_nursery: {nursery!r}\n'
f' parent_task={parent_task!r}\n'
f' |_child_nurseries: {child_nurseries!r}\n'
f')>\n'
)
def pformat_cs(
cs: CancelScope,
var_name: str = 'cs',

View File

@ -106,11 +106,20 @@ def at_least_level(
level: int|str,
) -> bool:
'''
Predicate to test if a given level is active.
Return whether `log.isEnabledFor()` permits `level`.
Accept integer log levels or their case-insensitive string names.
`CUSTOM_LEVELS` takes precedence over names registered with
`logging.addLevelName()`, including the built-in logging levels.
Unknown names raise `KeyError` with the uppercased name.
'''
if isinstance(level, str):
level: int = CUSTOM_LEVELS[level.upper()]
name: str = level.upper()
level = CUSTOM_LEVELS.get(name)
# Resolve stdlib registrations only on a CUSTOM_LEVELS miss.
if level is None:
level = logging.getLevelNamesMapping()[name]
return log.isEnabledFor(level)
@ -138,7 +147,7 @@ class StackLevelAdapter(LoggerAdapter):
def at_least_level(
self,
level: str,
level: int|str,
) -> bool:
return at_least_level(
log=self,
@ -323,26 +332,20 @@ def add_log_level(
add_log_level('IO', 21, 'purple')
# TODO IDEAs:
# -[ ] move to `.devx.pformat`?
# -[ ] do per task-name and actor-name color coding
# -[ ] unique color per task-id and actor-uuid
def pformat_task_uid(
id_part: str = 'tail'
):
id_part: str = 'tail',
*,
task: trio.lowlevel.Task|None = None,
) -> str:
'''
Return `str`-ified unique for a `trio.Task` via a combo of its
`.name: str` and `id()` truncated output.
Forward the logging entry point to devx.pformat's UID renderer.
'''
task: trio.Task = trio.lowlevel.current_task()
tid: str = str(id(task))
if id_part == 'tail':
tid_part: str = tid[-6:]
else:
tid_part: str = tid[:6]
# Import lazily: devx initialization itself imports tractor.log.
from .devx.pformat import pformat_task_uid as render_task_uid
return f'{task.name}[{tid_part}]'
return render_task_uid(id_part, task=task)
_curr_actor_no_exc = partial(

View File

@ -743,7 +743,38 @@ async def _invoke(
f'\n'
f'{pretty_struct.pformat(return_msg)}\n'
)
await chan.send(return_msg)
try:
await chan.send(return_msg)
except TransportClosed as ipc_err:
# The endpoint completed successfully. Failure to
# deliver its Return to a departed peer must not
# cancel sibling RPCs in Actor._service_tn.
# Keep this guard outside `await coro` so an
# endpoint's own transport errors still propagate.
actor_info: str = actor.pformat(privates=True)
ctx_info: str = ctx.pformat(
extra_fields={'scope_nursery': tn},
indent='',
)
scope_info: str = _pformat.pformat_cs(
rpc_ctx_cs,
var_name='ctx._scope',
)
chan_info: str = chan.pformat(privates=True)
ipc_info: str = _pformat.nest_from_op(
input_op='x=>',
text=repr(ipc_err),
)
log.warning(
'Final context result delivery failed: '
'peer disconnected\n'
f'\n'
f'{actor_info}\n'
f'{ctx_info}\n'
f'{scope_info}\n'
f'{chan_info}\n'
f'{ipc_info}\n'
)
# NOTE: this happens IFF `ctx._scope.cancel()` is
# called by any of,