docs: update runtime docs to reflect architecture changes from Cycles 1-3
Worker struct: TimerWheel → generic WorkerExtension. TickContext: registries moved behind RuntimeExtension. Phases 2.5/5.5/7 generalized. Ctx API split into core (6 methods) vs extension traits (swactor-std). Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
33df021afd
commit
803289c7dc
2 changed files with 71 additions and 66 deletions
|
|
@ -16,11 +16,11 @@ messages.
|
|||
│ │ │ │
|
||||
│ │ address_map: Arc<AddressMap> -- actor -> worker lookup │ │
|
||||
│ │ inbox_registry: Arc<InboxRegistry> -- external inbox delivery │ │
|
||||
│ │ name_registry: Arc<NameRegistry> -- name -> address lookup │ │
|
||||
│ │ monitor_registry: Arc<MonitorRegistry> -- death watch subscripts │ │
|
||||
│ │ group_registry: Arc<GroupRegistry> -- pub-sub actor groups │ │
|
||||
│ │ placement: Placement -- load-aware worker picker │ │
|
||||
│ │ worker_stats: Vec<Arc<WorkerStats>> -- atomic stat counters │ │
|
||||
│ │ extension: Option<Arc<dyn RuntimeExtension>> │ │
|
||||
│ │ (StdExtension holds: NameRegistry, MonitorRegistry, │ │
|
||||
│ │ GroupRegistry, WatchRegistry) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
|
|
@ -75,35 +75,42 @@ only way for actors to interact with the outside world.
|
|||
│ inner: &dyn ContextInner -- polymorphic dispatch │
|
||||
│ self_addr: ActorAddress -- address of the current actor │
|
||||
│ │
|
||||
│ ┌─ Public API ────────────────────────────────────────────────────────┐ │
|
||||
│ ┌─ Core API ─────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ctx.self_addr() -> ActorAddress │ │
|
||||
│ │ ctx.send(addr, msg) -> Result<(), Error> │ │
|
||||
│ │ ctx.spawn(actor) -> Result<ActorAddress, Error> │ │
|
||||
│ │ ctx.spawn_named(name, actor) -> Result<ActorAddress, Error> │ │
|
||||
│ │ ctx.spawn_restartable(a, f, max) -> Result<ActorAddress, Error> │ │
|
||||
│ │ ctx.stop_self() │ │
|
||||
│ │ ctx.stop_actor(addr) -> Result<(), Error> │ │
|
||||
│ │ ctx.where_is(name) -> Option<ActorAddress> │ │
|
||||
│ │ ctx.monitor(target) -> MonitorRef │ │
|
||||
│ │ ctx.demonitor(mref) │ │
|
||||
│ │ ctx.join_group(group) │ │
|
||||
│ │ ctx.leave_group(group) │ │
|
||||
│ │ ctx.publish(group, msg) -> usize │ │
|
||||
│ │ ctx.group_members(group) -> Vec<ActorAddress> │ │
|
||||
│ │ ctx.send_after_ticks(addr, msg, n) │ │
|
||||
│ │ ctx.send_interval_ticks(addr, msg, period) │ │
|
||||
│ │ ctx.extension() -> Option<&dyn RuntimeExtension> │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ Extension Traits (swactor-std) ──────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ CtxNaming: spawn_named, where_is │ │
|
||||
│ │ CtxMonitoring: monitor, demonitor │ │
|
||||
│ │ CtxWatching: watch, unwatch │ │
|
||||
│ │ CtxGroups: join_group, leave_group, publish, group_members │ │
|
||||
│ │ CtxTimers: send_after_ticks, send_interval_ticks │ │
|
||||
│ │ │ │
|
||||
│ │ These use ctx.extension() + downcast to StdExtension. │ │
|
||||
│ │ Also: spawn_restartable (via CtxNaming) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ ContextInner dispatch ─────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Five methods: send_any, spawn_any, request_stop, │ │
|
||||
│ │ post_worker_request, extension │ │
|
||||
│ │ │ │
|
||||
│ │ In single-threaded mode: inner = &Runtime │ │
|
||||
│ │ send → transfer_txs[wid], spawn → spawn_txs[wid] │ │
|
||||
│ │ │ │
|
||||
│ │ In multi-threaded mode: inner = &WorkerContext │ │
|
||||
│ │ send → pending_local (same worker) or transfer_txs (cross) │ │
|
||||
│ │ spawn → spawn_txs[target_wid] │ │
|
||||
│ │ post_worker_request → worker_requests (drained phase 5.5) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
|
|
@ -287,15 +294,22 @@ Actors can stop other actors from handlers:
|
|||
ctx.stop_actor(other_addr)?; // PoisonPill semantics — queued after existing msgs
|
||||
```
|
||||
|
||||
## Per-Worker Timers
|
||||
## Per-Worker Timers (swactor-std)
|
||||
|
||||
Deterministic tick-counting timers (not wall-clock):
|
||||
Deterministic tick-counting timers (not wall-clock). Requires `StdExtension`
|
||||
and the `CtxTimers` extension trait:
|
||||
|
||||
```
|
||||
use swactor_std::CtxTimers;
|
||||
|
||||
ctx.send_after_ticks(addr, msg, 5); // one-shot: fires after 5 ticks
|
||||
ctx.send_interval_ticks(addr, msg, 10); // repeating: every 10 ticks
|
||||
```
|
||||
|
||||
The `TimerWheel` lives as a per-worker extension (`WorkerExtension`),
|
||||
created by `StdExtension::create_worker_extension()`. Timer requests are
|
||||
dispatched via `ctx.post_worker_request()` and processed in phase 5.5.
|
||||
|
||||
## RuntimeHandle
|
||||
|
||||
Returned by `run()`. Holds `Arc<Runtime>` and the thread `JoinHandle`s.
|
||||
|
|
|
|||
|
|
@ -48,10 +48,12 @@
|
|||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ TimerWheel ────────────────────────────────────────────────────┐ │
|
||||
│ │ current_tick: u64 │ │
|
||||
│ │ once_timers: Vec<OnceTimer> -- fire_at, dest, msg │ │
|
||||
│ │ interval_timers: Vec<IntervalTimer> -- period, dest, clone_msg │ │
|
||||
│ ┌─ worker_ext: Option<Box<dyn WorkerExtension>> ─────────────────┐ │
|
||||
│ │ Per-worker extension state, created by RuntimeExtension │ │
|
||||
│ │ factory. StdExtension provides a TimerWheel here. │ │
|
||||
│ │ on_tick() → fire due messages (phase 2.5) │ │
|
||||
│ │ handle_request() → schedule timers etc. (phase 5.5) │ │
|
||||
│ │ gc_dead() → clean up dead actor state (phase 7) │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
|
|
@ -69,11 +71,9 @@ Lives on `Arc<Runtime>`, shared read-only across all worker threads.
|
|||
│ spawn_txs: &[Sender] -- one Sender per worker │
|
||||
│ placement: &Placement -- load-aware worker picker │
|
||||
│ inbox_registry: &InboxRegistry -- external Inbox<M> receivers │
|
||||
│ name_registry: &NameRegistry -- String -> ActorAddress │
|
||||
│ monitor_registry: &MonitorRegistry -- death watch subscriptions │
|
||||
│ group_registry: &GroupRegistry -- pub-sub actor groups │
|
||||
│ config: &RuntimeConfig -- budget, backoff, etc. │
|
||||
│ stats_hook: Option<&dyn Hook> -- per-tick stats callback │
|
||||
│ extension: Option<&dyn RuntimeExtension> -- shared ext │
|
||||
│ stats_hook: Option<&dyn StatsHook> -- per-tick stats callback │
|
||||
│ worker_threads: &[OnceLock<Thread>] -- for unpark on send/spawn │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
|
|
@ -147,19 +147,16 @@ Lives on `Arc<Runtime>`, shared read-only across all worker threads.
|
|||
│ └────────────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
│ v │
|
||||
│ PHASE 2.5 --- Fire Due Timers │
|
||||
│ PHASE 2.5 --- Fire Per-Worker Extension │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐│
|
||||
│ │ ││
|
||||
│ │ timers.fire() (advances tick counter, collects due messages) ││
|
||||
│ │ worker_ext.on_tick() → Vec<(dest, msg)> ││
|
||||
│ │ (StdExtension provides TimerWheel: advances tick, fires due) ││
|
||||
│ │ │ ││
|
||||
│ │ v ││
|
||||
│ │ for (dest, msg) in timer_msgs: ││
|
||||
│ │ ┌──────────────┬──────────────┬─────────────────┐ ││
|
||||
│ │ │ local actor │ other worker │ inbox/unknown │ ││
|
||||
│ │ │ │ │ │ ││
|
||||
│ │ │ pool.deliver │ transfer_tx │ inbox_registry │ ││
|
||||
│ │ │ │ + unpark │ .try_deliver() │ ││
|
||||
│ │ └──────────────┴──────────────┴─────────────────┘ ││
|
||||
│ │ for (dest, msg) in ext_msgs: ││
|
||||
│ │ route_to_pool_or_remote(pool, tc, dest, msg) ││
|
||||
│ │ local → pool.deliver | cross → transfer_tx | → inbox_registry ││
|
||||
│ │ ││
|
||||
│ └────────────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
|
|
@ -171,7 +168,7 @@ Lives on `Arc<Runtime>`, shared read-only across all worker threads.
|
|||
│ │ │ implements ContextInner │ ││
|
||||
│ │ │ pending_local: RefCell<Vec<(Addr, Box<Any>)>> │ ││
|
||||
│ │ │ stop_requests: RefCell<Vec<ActorAddress>> │ ││
|
||||
│ │ │ timer_requests: RefCell<Vec<TimerRequest>> │ ││
|
||||
│ │ │ worker_requests: RefCell<Vec<Box<dyn Any + Send>>> │ ││
|
||||
│ │ └────────────────────────────────────────────────────────────┘ ││
|
||||
│ │ ││
|
||||
│ │ for each (addr, slot) in pool: ││
|
||||
|
|
@ -215,10 +212,8 @@ Lives on `Arc<Runtime>`, shared read-only across all worker threads.
|
|||
│ │ │ spawn_txs[wid].send((addr, actor)) + unpark │ ││
|
||||
│ │ │ │ ││
|
||||
│ │ │ request_stop(addr): → stop_requests.push(addr) │ ││
|
||||
│ │ │ schedule_timer(req): → timer_requests.push(req) │ ││
|
||||
│ │ │ where_is(name): → name_registry.lookup(name) │ ││
|
||||
│ │ │ monitor(w, t): → monitor_registry.register(w, t) │ ││
|
||||
│ │ │ join_group(a, g): → group_registry.join(g, a) │ ││
|
||||
│ │ │ post_worker_request(r): → worker_requests.push(r) │ ││
|
||||
│ │ │ extension(): → tc.extension │ ││
|
||||
│ │ │ │ ││
|
||||
│ │ └────────────────────────────────────────────────────────────┘ ││
|
||||
│ │ ││
|
||||
|
|
@ -242,12 +237,12 @@ Lives on `Arc<Runtime>`, shared read-only across all worker threads.
|
|||
│ └────────────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
│ v │
|
||||
│ PHASE 5.5 --- Drain Timer Requests │
|
||||
│ PHASE 5.5 --- Drain Worker Extension Requests │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐│
|
||||
│ │ ││
|
||||
│ │ for request in timer_requests: ││
|
||||
│ │ Once { dest, msg, ticks } → timers.add_once(dest, msg, ticks) ││
|
||||
│ │ Interval { dest, msg, p } → timers.add_interval(dest, msg, p) ││
|
||||
│ │ for request in worker_requests: ││
|
||||
│ │ worker_ext.handle_request(request) ││
|
||||
│ │ (StdExtension: downcasts to TimerRequest, schedules timers) ││
|
||||
│ │ ││
|
||||
│ └────────────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
|
|
@ -272,23 +267,20 @@ Lives on `Arc<Runtime>`, shared read-only across all worker threads.
|
|||
│ │ stopping actors: call on_stop(&ctx) before removal ││
|
||||
│ │ poisoned actors: skip on_stop (state may be corrupt) ││
|
||||
│ │ ││
|
||||
│ │ for each dead (addr, reason): ││
|
||||
│ │ for each dead addr: ││
|
||||
│ │ address_map.remove(&addr) ││
|
||||
│ │ name_registry.unregister_by_addr(&addr) ││
|
||||
│ │ group_registry.cleanup(&addr) ││
|
||||
│ │ ││
|
||||
│ │ if extension installed: ││
|
||||
│ │ notifications = ext.on_actor_death(&dead) ││
|
||||
│ │ (StdExtension: emits Down/ActorExited, unregisters names, ││
|
||||
│ │ removes from groups, takes monitors) ││
|
||||
│ │ ext.cleanup_dead(&dead_addrs) ││
|
||||
│ │ route notifications via route_to_pool_or_remote() ││
|
||||
│ │ ││
|
||||
│ │ deliver any messages sent during on_stop callbacks ││
|
||||
│ │ ││
|
||||
│ │ emit Down notifications for monitored dead actors: ││
|
||||
│ │ for (addr, reason) in dead: ││
|
||||
│ │ watchers = monitor_registry.take_monitors(&addr) ││
|
||||
│ │ for each watcher: route Down { addr, reason } ││
|
||||
│ │ same-worker → pool.deliver ││
|
||||
│ │ cross-worker → transfer_tx + unpark ││
|
||||
│ │ inbox → inbox_registry.try_deliver ││
|
||||
│ │ monitor_registry.remove_watcher(&addr) ││
|
||||
│ │ ││
|
||||
│ │ timers.gc_dead_intervals(dead_addrs) ││
|
||||
│ │ worker_ext.gc_dead(&dead_addrs) ││
|
||||
│ │ (StdExtension: removes orphaned interval timers) ││
|
||||
│ │ ││
|
||||
│ └────────────────────────────────────────────────────────────────────┘│
|
||||
│ │
|
||||
|
|
@ -510,13 +502,12 @@ Who holds what:
|
|||
│ │ addr->wid │ │ load-aware │ │ addr->Sender│ │ AtomicBool │ │
|
||||
│ └─────┬─────┘ └──────┬─────┘ └──────┬──────┘ └──────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │NameRegistry │ │MonitorRegist.│ │GroupRegistry │ │
|
||||
│ │ name->addr │ │ watched-> │ │ group->addrs │ │
|
||||
│ │ addr->name │ │ watchers │ │ addr->groups │ │
|
||||
│ └─────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌─────┴───────────────┴──────────────┴───────────────────────────────┐ │
|
||||
│ ┌─ extension: Arc<dyn RuntimeExtension> ──────────────────────────┐ │
|
||||
│ │ StdExtension holds: NameRegistry, MonitorRegistry, │ │
|
||||
│ │ GroupRegistry, WatchRegistry (accessed via downcast) │ │
|
||||
│ └─────────────────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────┴──────────────────────────────────┐ │
|
||||
│ │ TickContext (borrows all above) │ │
|
||||
│ └──────────────────────────┬──────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
|
|
|
|||
Loading…
Reference in a new issue