feat: add ChildHandle and ActorId for type-erased actor management#164
feat: add ChildHandle and ActorId for type-erased actor management#164ElFantasma wants to merge 4 commits intomainfrom
Conversation
🤖 Kimi Code ReviewOverall Assessment: Good abstraction that cleanly unifies tasks and threads modes. Code is mostly correct with clean separation of concerns. Issues Found1. Double clone inefficiency in
|
🤖 Codex Code Review
I did not see other obvious correctness, race, or lifecycle regressions in the I could not run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewPR #164 Review:
|
Greptile SummaryThis PR introduces Confidence Score: 5/5Safe to merge; the only finding is a minor API inconsistency that does not affect correctness. No P0 or P1 issues found. The single P2 finding — a missing convenience method on Context — does not break any existing behavior or contract. The core design (ActorId uniqueness via Relaxed atomic fetch_add, ChildHandle equality by ActorId consistent with Hash, Completion enum unifying both backends, documented warning on wait_exit_blocking) is all correct. No files require special attention; the P2 gap in tasks/actor.rs and threads/actor.rs is a minor ergonomics improvement for future callers.
|
| Filename | Overview |
|---|---|
| concurrency/src/child_handle.rs | New file: defines ActorId (global atomic u64 starting at 1), Completion enum (Watch/Condvar variants), and ChildHandle type-erased supervision handle with stop/is_alive/exit_reason/wait_exit_blocking/wait_exit_async; includes 5 unit tests |
| concurrency/src/lib.rs | Adds public child_handle module and re-exports ActorId and ChildHandle at the crate root |
| concurrency/src/tasks/actor.rs | Adds ActorId field to ActorRef and Context, exposes id() on ActorRef, adds child_handle() convenience method and From<ActorRef> for ChildHandle; Context is missing a public id() method (P2) |
| concurrency/src/threads/actor.rs | Same additions as tasks/actor.rs for threads mode: ActorId on ActorRef/Context, id() on ActorRef, child_handle() and From impl; Context also missing public id() method (P2) |
Sequence Diagram
sequenceDiagram
participant Supervisor
participant ActorRef
participant ChildHandle
participant Actor
Supervisor->>ActorRef: spawn(actor) → ActorRef<A> (ActorId assigned)
Supervisor->>ActorRef: .child_handle()
ActorRef-->>Supervisor: ChildHandle (type-erased)
note over Supervisor,ChildHandle: Supervisor stores Vec<ChildHandle>
Supervisor->>ChildHandle: .id() → ActorId
Supervisor->>ChildHandle: .is_alive() → bool
Supervisor->>ChildHandle: .stop()
ChildHandle->>Actor: cancellation_token.cancel()
Actor-->>ChildHandle: sets completion (ExitReason)
Supervisor->>ChildHandle: .wait_exit_async().await
ChildHandle-->>Supervisor: ExitReason
Comments Outside Diff (1)
-
concurrency/src/tasks/actor.rs, line 109-114 (link)Context<A>missingid()accessorContextnow carriesid: ActorIdbut doesn't expose a publicid()method, unlikeActorRef<A>(which gainsid()at line 403 in this PR). Actors receivectx: &Context<Self>instarted(),stopped(), and allHandler::handle()calls. For the monitor/supervisor work this PR targets, actors need to know their own ID to register themselves — currently they must callctx.actor_ref().id(), which clones the full ref. The same gap exists inconcurrency/src/threads/actor.rs.Prompt To Fix With AI
This is a comment left during a code review. Path: concurrency/src/tasks/actor.rs Line: 109-114 Comment: **`Context<A>` missing `id()` accessor** `Context` now carries `id: ActorId` but doesn't expose a public `id()` method, unlike `ActorRef<A>` (which gains `id()` at line 403 in this PR). Actors receive `ctx: &Context<Self>` in `started()`, `stopped()`, and all `Handler::handle()` calls. For the monitor/supervisor work this PR targets, actors need to know their own ID to register themselves — currently they must call `ctx.actor_ref().id()`, which clones the full ref. The same gap exists in `concurrency/src/threads/actor.rs`. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: concurrency/src/tasks/actor.rs
Line: 109-114
Comment:
**`Context<A>` missing `id()` accessor**
`Context` now carries `id: ActorId` but doesn't expose a public `id()` method, unlike `ActorRef<A>` (which gains `id()` at line 403 in this PR). Actors receive `ctx: &Context<Self>` in `started()`, `stopped()`, and all `Handler::handle()` calls. For the monitor/supervisor work this PR targets, actors need to know their own ID to register themselves — currently they must call `ctx.actor_ref().id()`, which clones the full ref. The same gap exists in `concurrency/src/threads/actor.rs`.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: add ChildHandle and ActorId for ty..." | Re-trigger Greptile
Bot Review ResponsesFindings addressed in f9f5279 and 5cf0218. Kimi
Fixed in 5cf0218.
Fixed in 5cf0218. Changed to "stop, wait, check liveness."
Acknowledged — this is a defensive fallback for an edge case (the blocking thread itself panics). Logging would add a dependency on tracing in a low-level path. The behavior is documented.
Fixed in 5cf0218. Added doc note: "When used with threads-mode handles, this consumes a thread from tokio's blocking pool for the duration of the wait."
Already fixed in f9f5279. Added 4 integration tests: Codex
Already fixed in f9f5279. Uses
Already fixed in f9f5279. See test list above. Claude
Fixed in 5cf0218. Fields are moved, not cloned.
Already fixed in f9f5279. See Codex response above.
Fixed in 5cf0218. Added doc comment explaining the rationale: the watch sender was dropped without setting a reason, meaning the actor task was aborted externally (e.g., runtime shutdown).
Fixed in 5cf0218. Added
Already fixed in f9f5279. Greptile
|
Summary
ActorId— unique identity for actor instances, used by monitors and links to identify actors without knowing the concrete typeChildHandle— type-erased handle to a running actor providing supervision operations (stop(),is_alive(),exit_reason(),wait_exit_blocking(),wait_exit_async()) without needingActorRef<A>From<ActorRef<A>> for ChildHandleworks in both tasks and threads modesActorRef<A>andContext<A>now carry anActorIdassigned at spawn time, withid()accessors on bothexit_reasonexample with ChildHandle scenarios (type-erased stop, panic observation, heterogeneousVec<ChildHandle>)Building block for monitors (#131), links, and supervisor (#133). A supervisor manages children of different actor types —
ChildHandleis how it holds and operates on them uniformly.Key design decisions:
ActorIdfills the role of Erlang's Pid as a lightweight identity key, but is not the primary user-facing API (users interact throughActorRef<A>andChildHandle)CancelFn = Arc<dyn Fn() + Send + Sync>wraps both tokio and threads-mode cancel tokens (they're different types)Completionenum withWatch/Condvarvariants handles both execution modesChildHandleequality is byActorId, not pointer identitywait_exit_blockingusesblock_in_placeinside a tokio runtime to avoid panicsTest plan
cargo run -p exit_reason(scenarios 5-7 demonstrate ChildHandle)