swactor/docs/development_history/CYCLE_14_GROUPS.md
Developer 9ad90aed12 docs: add development history pages for all 19 cfuzz improvement cycles
One page per cycle covering motivation, competitor analysis, implementation,
design decisions, tests added, and results. Plus an overview/index page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:36:52 +00:00

4.3 KiB

Cycle 14: Actor Groups with Pub-Sub Broadcast — Development History

Commit: 4d18874 · 5 files · 307 insertions, 8 deletions


Motivation

Named registry (Cycle 12) provides one-to-one name→actor mapping. Many patterns require one-to-many: broadcasting events to subscribers, load distribution across a pool, or topic-based message routing. Actor groups provide this — a named collection of actors that can receive messages as a group.

Competitor Analysis

Framework Mechanism Key Design Auto-Cleanup
Erlang pg Scopes, join/leave/get_members Flat groups, atom keys Yes (on process exit)
Akka DistributedPubSub (mediator, topics) Cluster-wide pub-sub Yes (via DeathWatch)
Ractor pg module (join/leave/broadcast) Erlang-style, global Yes
Bastion Dispatcher Hierarchy-based routing Structural
Redis pub/sub Channels, patterns External service N/A
Swactor GroupRegistry Erlang pg-style, per-runtime Yes (on death)

Common Patterns Across Frameworks

  • Auto-cleanup on death (universal)
  • At-most-once delivery (no re-delivery guarantees)
  • String-based naming (flat, not hierarchical)
  • Lazy group creation/deletion (groups created on first join, deleted when empty)

Implementation

GroupRegistry (in delivery.rs)

  • Forward map: groups: RwLock<HashMap<String, HashSet<ActorAddress>>> — group → members
  • Reverse map: memberships: RwLock<HashMap<ActorAddress, HashSet<String>>> — actor → groups (for cleanup)
  • Groups auto-create on first join, auto-delete when empty

Runtime API

  • join_group(addr, name) — add actor to group
  • leave_group(addr, name) — remove actor from group
  • publish_to(group, msg) — broadcast to all group members
  • group_members(group) — list members
  • groups() — list all groups

Context API (from handler)

  • ctx.join_group(name) — join from inside handler
  • ctx.leave_group(name) — leave from inside handler
  • ctx.publish(group, msg) — broadcast from inside handler
  • ctx.group_members(group) — query from inside handler

Message Delivery

  • publish clones message at the typed level (Message: Clone), sends to each member via normal routing
  • Uses the same delivery pipeline as regular messages (pool.deliver, transfer_txs, inbox_registry)

Auto-Cleanup

  • group_registry.cleanup(&addr) called in cleanup_dead phase
  • Uses reverse map to find all groups the dead actor belonged to, removes from each

Key files modified: src/delivery.rs, src/runtime.rs, src/actor.rs, src/worker.rs, tests/runtime_api.rs

Design Decisions

  • Erlang pg model — flat groups with string keys. Simpler than Akka's mediator/topic model, and sufficient for the common use cases (event broadcasting, worker pools).
  • Clone-based broadcast — message is cloned for each recipient. This is O(N) but straightforward and type-safe. Alternative (shared Arc) would complicate the message pipeline.
  • Reverse map for cleanup — without it, cleaning up a dead actor would require scanning all groups. O(1) per group membership vs O(groups) scan.
  • Lazy lifecycle — groups are created implicitly on first join and deleted when the last member leaves. No explicit create/delete API needed. Matches Erlang pg.
  • publish requires Message: Clone — enforced at the type level. If a message type isn't Clone, it can't be broadcast. This is a compile-time safety guarantee.

Tests Added

9 new tests (113 → 122 total):

  • group_members_returns_joined_actors — join + query returns members
  • empty_group_returns_no_members — nonexistent group → empty set
  • publish_broadcasts_to_all_members — 2 members, both receive the message
  • leave_group_stops_receiving_publishes — leave → excluded from future broadcasts
  • dead_actor_auto_removed_from_group — stop → removed from group
  • actor_removed_from_all_groups_on_death — multi-group membership cleanup
  • empty_group_auto_deleted — last member leaves → group removed from groups()
  • ctx_join_group_from_handler — join via on_start
  • ctx_publish_broadcasts_from_handler — publish via handler

Result

  • 122 tests pass (115 behavioral + 7 proptest)
  • All workspace crates compile, zero warnings