diff --git a/ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.md b/ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.md new file mode 100644 index 00000000..a1bae741 --- /dev/null +++ b/ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.md @@ -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. diff --git a/ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.raw.md b/ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.raw.md new file mode 100644 index 00000000..e0ac546d --- /dev/null +++ b/ai/prompt-io/opencode/20260818T193002Z_bf06b4f8_prompt_io.raw.md @@ -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. diff --git a/tractor/_context.py b/tractor/_context.py index d538e361..089442ef 100644 --- a/tractor/_context.py +++ b/tractor/_context.py @@ -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) diff --git a/tractor/runtime/_rpc.py b/tractor/runtime/_rpc.py index d90cb658..483888a2 100644 --- a/tractor/runtime/_rpc.py +++ b/tractor/runtime/_rpc.py @@ -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 diff --git a/tractor/runtime/_runtime.py b/tractor/runtime/_runtime.py index 67cae017..a1788336 100644 --- a/tractor/runtime/_runtime.py +++ b/tractor/runtime/_runtime.py @@ -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,