core(actor): rebuild Actor trait, lifecycle hooks & run-loop (#116)#142
Merged
Conversation
…nResult amendment
…ine (#116) Scoped cargo-mutants over the #116 spine (kind.rs, spawn.rs, actor_ref.rs, mod.rs) surfaced 7 missed + 3 timeout mutants. Add/strengthen tests to kill each; no production behavior changed. Missed (now caught): - Actor::name default stubbed to ""/"xyzzy" -> assert name() contains the concrete type name. - Debug impls (ActorRef, WeakActorRef, RunResult, PreparedActor) stubbed to an empty formatter -> assert the rendered debug names the struct/variant. Timeouts (now caught, converted to fail-fast): stop/kill/stop-flag mutants turned hangs into 20s harness timeouts because tests that rely on stop()/kill()/`*stop` to terminate would park forever. Bound every such await (cancel_finishes, messages_during_on_start, stop_flag, kill) in a 5s tokio::time::timeout, and add stop_terminates_idle_actor, so the mutant fails fast instead of hanging. log_on_stop_outcome is diagnostic-only (eprintln, no state) and its "replace with ()" mutant is unobservable without capturing stderr; skipped via .cargo/mutants.toml exclude_re (config-file equivalent of #[mutants::skip], avoiding a no-op production dependency on the `mutants` crate). Scoped mutants: 0 missed / 0 timeout on the four actor files and on error.rs (from_panic_any covered).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #116.
Implements #116 — the local actor spine in
bombay-core: theActortrait, its lifecycle hooks, the run-loop that drives it, a minimalActorRefscaffold, and the spawn entry points. Send-saturated (MaybeSend#9 deferred); rebuilt from the kameo reference, stripped of the supervision/links/console entanglement that belongs to later cards.Spec:
docs/superpowers/specs/2026-07-05-116-actor-trait-loop-design.md· Plan:docs/superpowers/plans/2026-07-05-116-actor-trait-loop.mdPublic surface (bombay-core)
Actor: Mailboxed<Msg: Msg>—on_start/handle/on_panic(infallible, stop-only) /on_stop(Err logged, never unwrapped).on_start→loop { CancellationToken::run_until_cancelled(recv) }→on_stop, fourcatch_unwindsites.ActorRef/WeakActorRef(minimal scaffold: id + mailbox + cancel + abort),PreparedActor,RunResult(Stopped/StartupFailed/Killed),Spawnext-trait.PanicError::from_panic_anyadded toerror.rs.Design decisions (some diverge from the card's original wording — all recorded on the issue)
VecDeque—await on_startfully; the bounded flume mailbox is the FIFO buffer (a side queue would be unbounded, contradicting the mailbox card).run_until_cancelledinstead ofselect!— a plainmatchthat wraps only the receive, so an in-flight handler always finishes before a stop is observed (finish-current-then-stop, no drain).&mut selfkept, poisoned-on-panic — handler panic caught →on_panic→on_stopstill runs withPanickedon the torn state (resource-release only) → discard. No resume. A returnedErrroutes toPanicked(HandlerPanic)too.kill()=futures::Abortablearound the whole lifecycle (uniform for run + spawn) →RunResult::Killed, skipson_stop.Testing — verified to the actor-system bar, not just coverage
cargo-mutants): zero survivors on all core(actor): rebuild from kameo reference — Actor trait, lifecycle hooks, run-loop #116 code. 10 real survivors found and killed — including stop/kill mutants that hung rather than failed (now fail-fast via timeouts). One equivalent mutant (stderr-only diagnostic) excluded with justification in.cargo/mutants.toml.tests/dst_races.rs): 8 deterministic stop/cancel/kill/startup race tests, deterministic across 10 runs.tests/invariants.rs), I1–I23 — every invariant measured, none violated: single-writer mutual exclusion (max_concurrent == 1under 8 concurrent senders), macro-step atomicity, no-reentrancy/self-send-queued, FIFO + exactly-once, lifecycle order + exactly-once hooks, panic containment + stop-reason fidelity, fault isolation (one crash doesn't touch another actor or the runtime), alive-window, send-to-dead-fails, backpressure + capacity-freed-by-draining, termination/no-hang.nix flake checkgreen (clippy at the god-levelall+pedantic+nurserydeny bar, fmt, nextest, doc, doctest, deny, typos).Deferred invariants — filed, not lost
The full actor-system invariant catalog is posted on #116; the invariants whose machinery doesn't exist yet are cross-linked to their cards, each marked "must land as a direct test when built": supervision/restart/death-watch → #120/#8, ref-count-stop/
Recipient→ #117, AID uniqueness/addressability → #121, passivation → #20, durability/rehydrate → #12, remote/location-transparency → #67.Carry-forwards (non-blocking, filed on cards)
on_stoppanic on a Normal stop returns a tornactorwithreason: Normal(logged only) — surface programmatically in core(supervision): rebuild from kameo reference — links, death-watch, restart strategies #120.next_actor_idwraps silently at 2^64 (replaced by the AID) → core(identity): identity-first ActorId — AID/SAID/delegation, key-expr addressable #121.mailbox.rsdoc still describes the drain-on-Stop path core(actor): rebuild from kameo reference — Actor trait, lifecycle hooks, run-loop #116 removed → core(actor_ref): rebuild from kameo reference — ActorRef / Weak / Recipient #117.ActorIdisEqbut notHash/ no raw accessor → core(identity): identity-first ActorId — AID/SAID/delegation, key-expr addressable #121.Test plan
nix flake checkgreen (build + clippy + fmt + nextest + doc + doctest + deny + typos)cargo-mutantszero survivors on core(actor): rebuild from kameo reference — Actor trait, lifecycle hooks, run-loop #116 code (nix build .#mutantsblocked only by pre-existingmailbox.rstimeouts, core(mailbox): principal-grade redesign — evaluate channel primitive + composable API #133)