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
132
133
134
//! # 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.
//!
//! **one slot = one logical key** *(`slot key = TaskSpec::slot()`, which defaults to the task name and can be overridden via `TaskSpec::with_slot`)*.
//! Several differently-named tasks may therefore share a single slot (sequentially).
//!
//! ## Note:
//!
//! Task **names stay globally unique** in the registry - submitting a name that is already running will be rejected by the registry (`TaskAddFailed`), which frees the slot and advances its queue.
//!
//! ## 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.slot()` defaults to `name()`, override with `with_slot` (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 slot)
//!
//! 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` (feature = `logging`).
//!
//! ## 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;