Compare commits
4 Commits
a966928dea
...
b64c89f05a
| Author | SHA1 | Date |
|---|---|---|
|
|
b64c89f05a | |
|
|
bf2bb1c9a6 | |
|
|
0553fcf88c | |
|
|
9a0f220fd5 |
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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',
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue