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
|
Previously `to_actor.run()` treated an actor-runtime cancel ack
|
||||||
as process reaping, so the call returned while the child monitor
|
as process reaping, so the call returned while the child monitor
|
||||||
and its `ActorNursery._children` record remained alive until the
|
and its `ActorNursery` child/reap bookkeeping remained alive until
|
||||||
entire nursery exited. The assertion inside the still-open
|
the entire nursery exited. The assertions inside the still-open
|
||||||
nursery proves child-process joining and record removal now
|
nursery prove child-process joining and removal from all three
|
||||||
complete before the one-shot call returns.
|
mappings complete before the one-shot call returns.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
async with tractor.open_nursery() as an:
|
async with tractor.open_nursery() as an:
|
||||||
|
|
@ -214,6 +214,8 @@ async def test_spawn_from_caller_nursery(
|
||||||
an=an,
|
an=an,
|
||||||
) == 11
|
) == 11
|
||||||
assert not an._children
|
assert not an._children
|
||||||
|
assert not an._child_reap_requests
|
||||||
|
assert not an._child_reaped
|
||||||
|
|
||||||
|
|
||||||
@tractor_test
|
@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
|
gate and then waited forever for a still-running process. This
|
||||||
test forces that exact result without cancelling the actor, caps
|
test forces that exact result without cancelling the actor, caps
|
||||||
the call to detect the former hang and verifies the child monitor
|
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(
|
async def cancel_without_ack(
|
||||||
|
|
@ -256,6 +258,8 @@ async def test_cancel_ack_failure_hard_reaps_child(
|
||||||
an=an,
|
an=an,
|
||||||
) == 21
|
) == 21
|
||||||
assert not an._children
|
assert not an._children
|
||||||
|
assert not an._child_reap_requests
|
||||||
|
assert not an._child_reaped
|
||||||
|
|
||||||
|
|
||||||
def test_cancel_actor_timeout_closes_blocked_send():
|
def test_cancel_actor_timeout_closes_blocked_send():
|
||||||
|
|
|
||||||
|
|
@ -370,11 +370,18 @@ class ActorNursery:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
self._children.pop(uid, None)
|
self._children.pop(uid, None)
|
||||||
self._child_reap_requests.pop(uid, None)
|
reap_request: trio.Event|None = (
|
||||||
|
self._child_reap_requests.pop(uid, None)
|
||||||
|
)
|
||||||
reaped: trio.Event|None = self._child_reaped.pop(
|
reaped: trio.Event|None = self._child_reaped.pop(
|
||||||
uid,
|
uid,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
assert (
|
||||||
|
(reap_request is None)
|
||||||
|
==
|
||||||
|
(reaped is None)
|
||||||
|
)
|
||||||
if reaped is not None:
|
if reaped is not None:
|
||||||
reaped.set()
|
reaped.set()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue