Centralize `Context` registry removal

Derive the `Actor._contexts` key from each `Context` in one
idempotent `Actor._drop_context()` helper instead of reconstructing
the peer UID and CID at every teardown site.

Use the helper for caller-side context exit and preserve a strict
identity assertion when the callee-side RPC task deregisters
itself. Keep channel closure and cancellation shielding with their
existing lifecycle owners.

Caught-during: review remediation
Found-via: staged P2 lifecycle review

Review: PR #481 (opencode)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-4956692120

Prompt-IO: ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
drop_ria_nursery
Gud Boi 2026-08-18 20:21:55 -04:00
parent 4d07965a2a
commit 754458ee7d
5 changed files with 75 additions and 8 deletions

View File

@ -0,0 +1,37 @@
---
model: openai/gpt-5.6-sol
service: opencode
session: ses_3e4c90d3eafeqHEtRYSIHgHhpA
timestamp: 2026-08-18T19:30:02Z
git_ref: bf06b4f8
scope: code
substantive: true
raw_file: 20260818T193002Z_bf06b4f8_prompt_io.raw.md
---
## Prompt
Distill repeated `Actor._contexts.pop()` machinery into a wrapper like
the RPC-task registration helper so future teardown sites do not keep
reconstructing the context-registry key independently. Preserve the
existing lifecycle-specific cleanup behavior.
## Response summary
Add idempotent `Actor._drop_context()` registry removal keyed from the
context's own channel and CID. Use it for caller context teardown and
the strict callee-side RPC deregistration path.
## Files changed
- `tractor/runtime/_runtime.py` - own context-registry removal.
- `tractor/runtime/_rpc.py` - use the helper for callee teardown.
- `tractor/_context.py` - use the helper after caller teardown.
## Human edits
The human identified the repeated registry-pop code and requested a
central primitive analogous to `_register_rpc_task()`. The agent first
suggested an async helper that also closed receive channels; the final
design was narrowed to registry removal only so each lifecycle owner
retains its existing closure, debugger, shielding, and error policy.

View File

@ -0,0 +1,18 @@
---
model: openai/gpt-5.6-sol
service: opencode
timestamp: 2026-08-18T19:30:02Z
git_ref: bf06b4f8
diff_cmd: git diff HEAD~1..HEAD
---
Repeated teardown sites reconstruct the `Actor._contexts` registry
key from a portal channel and context ID before popping it. Add an
idempotent actor-owned helper deriving the key from the context itself,
then route caller and callee context teardown through that helper.
> `git diff HEAD~1..HEAD -- tractor/runtime/_runtime.py tractor/runtime/_rpc.py tractor/_context.py`
Keep receive-channel closure and cancellation shielding in each
lifecycle owner so the helper centralizes registry machinery without
changing their teardown ordering.

View File

@ -2625,10 +2625,7 @@ async def open_context_from_portal(
f'uid: {uid}\n'
f'cid: {ctx.cid}\n'
)
portal.actor._contexts.pop(
(uid, ctx.cid),
None,
)
portal.actor._drop_context(ctx)
# XXX revert to prior IPC-task-ctx scope
_ctxvar_Context.reset(prior_ctx_tok)

View File

@ -879,10 +879,8 @@ async def _invoke(
# don't pop the local context until we know the
# associated child isn't in debug any more
await debug.maybe_wait_for_debugger()
ctx: Context = actor._contexts.pop((
chan.aid.uid,
cid,
))
dropped_ctx: Context|None = actor._drop_context(ctx)
assert dropped_ctx is ctx
logmeth: Callable = log.runtime
merr: Exception|None = ctx.maybe_error

View File

@ -753,6 +753,23 @@ class Actor:
return ctx
def _drop_context(
self,
ctx: Context,
) -> Context|None:
'''
Remove `ctx` from this actor's IPC context registry.
Teardown paths can converge after normal return, cancellation
or startup failure, so registry removal is idempotent.
'''
peer_uid: tuple[str, str] = ctx.chan.aid.uid
return self._contexts.pop(
(peer_uid, ctx.cid),
None,
)
async def start_remote_task(
self,
chan: Channel,