diff --git a/docs/runtime/runtime.md b/docs/runtime/runtime.md index 05655fe..02ff659 100644 --- a/docs/runtime/runtime.md +++ b/docs/runtime/runtime.md @@ -16,11 +16,11 @@ messages. │ │ │ │ │ │ address_map: Arc -- actor -> worker lookup │ │ │ │ inbox_registry: Arc -- external inbox delivery │ │ -│ │ name_registry: Arc -- name -> address lookup │ │ -│ │ monitor_registry: Arc -- death watch subscripts │ │ -│ │ group_registry: Arc -- pub-sub actor groups │ │ │ │ placement: Placement -- load-aware worker picker │ │ │ │ worker_stats: Vec> -- atomic stat counters │ │ +│ │ extension: Option> │ │ +│ │ (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 │ │ -│ │ ctx.spawn_named(name, actor) -> Result │ │ -│ │ ctx.spawn_restartable(a, f, max) -> Result │ │ │ │ ctx.stop_self() │ │ │ │ ctx.stop_actor(addr) -> Result<(), Error> │ │ -│ │ ctx.where_is(name) -> Option │ │ -│ │ 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 │ │ -│ │ 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` and the thread `JoinHandle`s. diff --git a/docs/runtime/worker-thread.md b/docs/runtime/worker-thread.md index 3916f9e..9b6ab3c 100644 --- a/docs/runtime/worker-thread.md +++ b/docs/runtime/worker-thread.md @@ -48,10 +48,12 @@ │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ -│ ┌─ TimerWheel ────────────────────────────────────────────────────┐ │ -│ │ current_tick: u64 │ │ -│ │ once_timers: Vec -- fire_at, dest, msg │ │ -│ │ interval_timers: Vec -- period, dest, clone_msg │ │ +│ ┌─ worker_ext: Option> ─────────────────┐ │ +│ │ 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`, 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 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] -- for unpark on send/spawn │ │ │ └────────────────────────────────────────────────────────────────────────┘ @@ -147,19 +147,16 @@ Lives on `Arc`, 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 ││ │ │ ││ │ └────────────────────────────────────────────────────────────────────┘│ │ │ │ @@ -169,9 +166,9 @@ Lives on `Arc`, shared read-only across all worker threads. │ │ ││ │ │ ┌─ WorkerContext (on stack) ─────────────────────────────────┐ ││ │ │ │ implements ContextInner │ ││ -│ │ │ pending_local: RefCell)>> │ ││ -│ │ │ stop_requests: RefCell> │ ││ -│ │ │ timer_requests: RefCell> │ ││ +│ │ │ pending_local: RefCell)>> │ ││ +│ │ │ stop_requests: RefCell> │ ││ +│ │ │ worker_requests: RefCell>> │ ││ │ │ └────────────────────────────────────────────────────────────┘ ││ │ │ ││ │ │ for each (addr, slot) in pool: ││ @@ -214,11 +211,9 @@ Lives on `Arc`, shared read-only across all worker threads. │ │ │ address_map.insert(addr, wid) │ ││ │ │ │ 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) │ ││ +│ │ │ request_stop(addr): → stop_requests.push(addr) │ ││ +│ │ │ post_worker_request(r): → worker_requests.push(r) │ ││ +│ │ │ extension(): → tc.extension │ ││ │ │ │ │ ││ │ │ └────────────────────────────────────────────────────────────┘ ││ │ │ ││ @@ -242,12 +237,12 @@ Lives on `Arc`, 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`, 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 ──────────────────────────┐ │ +│ │ StdExtension holds: NameRegistry, MonitorRegistry, │ │ +│ │ GroupRegistry, WatchRegistry (accessed via downcast) │ │ +│ └─────────────────────────────────┬───────────────────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────────┴──────────────────────────────────┐ │ │ │ TickContext (borrows all above) │ │ │ └──────────────────────────┬──────────────────────────────────────────┘ │ │ │ │