Assert paired `ActorNursery` reap state
`._mark_child_reaped()` previously discarded the reap-request event without checking that its completion-event peer existed. A one-sided entry would silently lose process-reap synchronization. Capture both pops and assert paired presence while allowing the valid both-absent startup-failure path. Keep an unset request valid because backend cancellation can reap immediately after registration. Extend graceful and failed-cancel-ack runtime tests to require all child and reap mappings empty before `to_actor.run()` returns. Review: PR #481 (goodboy) https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328 Prompt-IO: ai/prompt-io/opencode/20260824T223614Z_88d538e3_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/to_actor_subpkg
parent
88d538e3a6
commit
2f86dd1a33
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
model: openai/gpt-5.6-sol
|
||||
service: opencode
|
||||
session: d9d7df2c-7044-463f-8768-ec024718eac9
|
||||
timestamp: 2026-08-24T22:36:14Z
|
||||
git_ref: 88d538e3
|
||||
scope: code
|
||||
substantive: true
|
||||
raw_file: 20260824T223614Z_88d538e3_prompt_io.raw.md
|
||||
---
|
||||
|
||||
## Prompt
|
||||
|
||||
Continue PR #481 review remediation after committing the shared
|
||||
`Context.cancel()` deadline fix. The human accepted the proposed
|
||||
child-reap bookkeeping invariant, asking only that the first fix receive
|
||||
its own commit plan and commit before this update began.
|
||||
|
||||
## Response summary
|
||||
|
||||
Check that `ActorNursery` removes its paired reap-coordination entries
|
||||
together while preserving valid pre-registration and immediate-cancel
|
||||
paths. Extend the existing real-runtime reap tests to prove all three
|
||||
child bookkeeping mappings are empty before `to_actor.run()` returns.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `tractor/runtime/_supervise.py` - assert paired reap-map cleanup.
|
||||
- `tests/test_to_actor.py` - verify graceful and hard-reap bookkeeping.
|
||||
|
||||
## Human edits
|
||||
|
||||
The human explicitly accepted this invariant update but directed the
|
||||
preceding cancellation fix to be planned and committed as a separate
|
||||
boundary first. No direct source-line edits were made by the human.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
model: openai/gpt-5.6-sol
|
||||
service: opencode
|
||||
timestamp: 2026-08-24T22:36:14Z
|
||||
git_ref: 88d538e3
|
||||
diff_cmd: git diff HEAD~1..HEAD
|
||||
---
|
||||
|
||||
Implement the approved PR #481 child-reap bookkeeping update after the
|
||||
preceding `Context.cancel()` fix was committed separately.
|
||||
|
||||
> `git diff HEAD~1..HEAD -- tractor/runtime/_supervise.py`
|
||||
|
||||
`ActorNursery._mark_child_reaped()` captures both reap-coordination
|
||||
entries and asserts that they are either both present or both absent.
|
||||
It intentionally does not require the reap-request event to be set,
|
||||
because backend cancellation can reap immediately after registration.
|
||||
|
||||
> `git diff HEAD~1..HEAD -- tests/test_to_actor.py`
|
||||
|
||||
Existing real-runtime graceful and hard-reap tests verify that
|
||||
`ActorNursery._children`, `ActorNursery._child_reap_requests`, and
|
||||
`ActorNursery._child_reaped` are all empty before the one-shot call
|
||||
returns.
|
||||
|
||||
Run focused bookkeeping and real-runtime reap tests after the edit.
|
||||
|
|
@ -201,10 +201,10 @@ async def test_spawn_from_caller_nursery(
|
|||
|
||||
Previously `to_actor.run()` treated an actor-runtime cancel ack
|
||||
as process reaping, so the call returned while the child monitor
|
||||
and its `ActorNursery._children` record remained alive until the
|
||||
entire nursery exited. The assertion inside the still-open
|
||||
nursery proves child-process joining and record removal now
|
||||
complete before the one-shot call returns.
|
||||
and its `ActorNursery` child/reap bookkeeping remained alive until
|
||||
the entire nursery exited. The assertions inside the still-open
|
||||
nursery prove child-process joining and removal from all three
|
||||
mappings complete before the one-shot call returns.
|
||||
|
||||
'''
|
||||
async with tractor.open_nursery() as an:
|
||||
|
|
@ -214,6 +214,8 @@ async def test_spawn_from_caller_nursery(
|
|||
an=an,
|
||||
) == 11
|
||||
assert not an._children
|
||||
assert not an._child_reap_requests
|
||||
assert not an._child_reaped
|
||||
|
||||
|
||||
@tractor_test
|
||||
|
|
@ -231,7 +233,7 @@ async def test_cancel_ack_failure_hard_reaps_child(
|
|||
gate and then waited forever for a still-running process. This
|
||||
test forces that exact result without cancelling the actor, caps
|
||||
the call to detect the former hang and verifies the child monitor
|
||||
removes its `ActorNursery._children` record before returning.
|
||||
removes every `ActorNursery` child/reap entry before returning.
|
||||
|
||||
'''
|
||||
async def cancel_without_ack(
|
||||
|
|
@ -256,6 +258,8 @@ async def test_cancel_ack_failure_hard_reaps_child(
|
|||
an=an,
|
||||
) == 21
|
||||
assert not an._children
|
||||
assert not an._child_reap_requests
|
||||
assert not an._child_reaped
|
||||
|
||||
|
||||
def test_cancel_actor_timeout_closes_blocked_send():
|
||||
|
|
|
|||
|
|
@ -370,11 +370,18 @@ class ActorNursery:
|
|||
|
||||
'''
|
||||
self._children.pop(uid, None)
|
||||
reap_request: trio.Event|None = (
|
||||
self._child_reap_requests.pop(uid, None)
|
||||
)
|
||||
reaped: trio.Event|None = self._child_reaped.pop(
|
||||
uid,
|
||||
None,
|
||||
)
|
||||
assert (
|
||||
(reap_request is None)
|
||||
==
|
||||
(reaped is None)
|
||||
)
|
||||
if reaped is not None:
|
||||
reaped.set()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue