1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! # Controller: slot-based admission & start-on-`TaskRemoved`
//!
//! The **controller** is a thin policy layer (wrapper) over `TaskSpec` submission.
//! It enforces per-slot admission rules (`Queue`, `Replace`, `DropIfRunning`) and starts
//! the *next* task **only after** a terminal `TaskRemoved` event is observed on the runtime bus.
//!
//! In this model, **one slot = one task name** *(`slot key = TaskSpec::name()`)*.
//!
//! ## Role in Taskvisor
//!
//! The controller accepts `ControllerSpec`, unwraps its `TaskSpec`, applies admission rules,
//! and delegates `add/remove` to the Supervisor.
//! It subscribes to the Bus and advances slots strictly on `TaskRemoved` to avoid
//! registry races and double starts.
//!
//! ```text
//! ┌───────────────────────────────┐
//! │ Application code │
//! │ handle.submit(ControllerSpec) │
//! └──────────────┬────────────────┘
//! │
//! ▼
//! ┌──────────────────┐
//! │ Controller │ (per-slot admission FSM)
//! └─────────┬────────┘
//! unwraps & applies policy
//! ▼
//! ┌──────────────────┐
//! │ TaskSpec │ (from ControllerSpec)
//! └─────────┬────────┘
//! add/remove
//! ▼
//! ┌──────────────────┐
//! │ Supervisor │
//! └─────────┬────────┘
//! publishes runtime events
//! ▼
//! ┌──────────────────┐ subscribes
//! │ Bus │◄────────────── Controller
//! └─────────┬────────┘
//! ▼
//! Task Actors
//! ```
//!
//! **Flow summary**
//! 1. Application calls `handle.submit(ControllerSpec)` (via `SupervisorHandle`).
//! 2. Controller unwraps `TaskSpec` and applies `Admission` rules.
//! 3. If accepted, controller calls `Supervisor::add_task(TaskSpec)` or requests remove.
//! 4. On terminal `TaskRemoved` (via Bus), the slot becomes `Idle` and the next queued task (if any) is started.
//!
//! ## Per-slot model
//!
//! - **Key**: `task_spec.name()` (exactly one running task per slot).
//! - **State**: `Idle | Running | Terminating`, with a FIFO queue per slot.
//! - **Replace (latest-wins)**: does **not** grow the queue; it **replaces the head** (the immediate successor).
//! The next task actually starts **only after `TaskRemoved`**.
//!
//! ```text
//! State machine (per task name)
//!
//! Idle ── submit(any) → start → Running
//! ▲ │
//! │ ┌─────────┼──────────────┐
//! │ │ │ │
//! │ submit(Queue) submit(Replace) submit(DropIfRunning)
//! │ → enqueue → enqueue head → reject (silent)
//! │ → Terminating
//! │ │
//! │ submit(Queue) → enqueue
//! │ submit(Replace) → replace head
//! │ submit(DropIfRunning) → reject
//! │ │
//! └──────── on TaskRemoved ◄─────────┘
//! ├─ queue empty → Idle (slot removed)
//! └─ queue has next → start next → Running
//! ```
//!
//! Queue operations
//! - **Queue**: push to tail (FIFO).
//! - **Replace**: replace head (latest-wins), not increasing depth.
//!
//! ```text
//! Queue example (head on the left):
//! [ next, a, b ] --(Replace c)--> [ c, a, b ] --(Replace d)--> [ d, a, b ]
//! (head replaced) (head replaced)
//! ```
//!
//! ## Why gate on `TaskRemoved`
//!
//! `ActorExhausted/ActorDead` may arrive **before** full deregistration of the actor.
//! Starting the next task on those signals can race the registry and cause`task_already_exists`.
//!
//! Gating advancement on **`TaskRemoved`** prevents double-adds.
//!
//! ## Concurrency & scalability
//!
//! - `DashMap<Arc<str>, Arc<Mutex<SlotState>>>` avoids global map lock contention.
//! - Per-slot `Mutex` ensures updates to one slot don’t block others.
//!
//! ## Public surface
//!
//! - Configure via `Supervisor::builder(..).with_controller(ControllerConfig)`.
//! - Submit via `handle.submit(ControllerSpec::{queue, replace, drop_if_running}(...))`.
//! - Policies: [`AdmissionPolicy`] = `Queue | Replace | DropIfRunning`.
//! - Controller emits [`ControllerSubmitted`](crate::EventKind::ControllerSubmitted),
//! [`ControllerRejected`](crate::EventKind::ControllerRejected), and
//! [`ControllerSlotTransition`](crate::EventKind::ControllerSlotTransition) events;
//! readable with [`LogWriter`](crate::LogWriter).
//!
//! ## Invariants
//!
//! - At most **one** running task per slot.
//! - Slots advance to next task **only** after `TaskRemoved`.
//! - `Replace` is **latest-wins** (head replace); `Queue` is FIFO.
pub use AdmissionPolicy;
pub use ControllerConfig;
pub use Controller;
pub use ControllerError;
pub use ControllerSpec;