1use std::collections::{HashMap, VecDeque};
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::{Arc, Condvar, Mutex};
4use std::thread::JoinHandle;
5use std::time::{Duration, Instant};
6
7use crate::bytecode::Value;
8
9use super::error::{report_fault, SpawnError};
10use super::handle::FlowHandle;
11use super::process::{FlowId, FlowOutcome, RestartPolicy};
12use super::runtime::RuntimeSpawner;
13use super::sync_lock;
14
15const DEFAULT_MAX_RESTARTS: u32 = 3;
20const DEFAULT_MAX_PERIOD: Duration = Duration::from_secs(5);
21
22#[derive(Clone, Debug)]
28pub struct ChildSpec {
29 pub name: String,
30 pub function: u32,
31 pub args: Vec<Value>,
32 pub restart: RestartPolicy,
33}
34
35impl ChildSpec {
36 pub fn new(name: impl Into<String>, function: u32) -> Self {
37 ChildSpec {
38 name: name.into(),
39 function,
40 args: Vec::new(),
41 restart: RestartPolicy::OnFailure,
42 }
43 }
44
45 pub fn args(mut self, args: Vec<Value>) -> Self {
46 self.args = args;
47 self
48 }
49
50 pub fn restart(mut self, restart: RestartPolicy) -> Self {
51 self.restart = restart;
52 self
53 }
54}
55
56#[derive(Clone, Debug)]
63pub struct SupervisorConfig {
64 pub max_restarts: u32,
68 pub max_period: Duration,
69}
70
71impl Default for SupervisorConfig {
72 fn default() -> Self {
73 SupervisorConfig {
74 max_restarts: DEFAULT_MAX_RESTARTS,
75 max_period: DEFAULT_MAX_PERIOD,
76 }
77 }
78}
79
80struct ChildExit {
81 id: FlowId,
82 outcome: FlowOutcome,
83}
84
85struct LiveChild {
86 spec: ChildSpec,
87}
88
89struct Inner {
90 spawner: RuntimeSpawner,
91 config: SupervisorConfig,
92 events: Mutex<VecDeque<ChildExit>>,
93 cvar: Condvar,
94 children: Mutex<HashMap<FlowId, LiveChild>>,
95 restart_times: Mutex<VecDeque<Instant>>,
96 intensity_exceeded: AtomicBool,
97 shutdown: AtomicBool,
98}
99
100#[derive(Clone)]
104pub(crate) struct SupervisorLink {
105 inner: Arc<Inner>,
106}
107
108impl SupervisorLink {
109 pub(crate) fn notify(&self, id: FlowId, outcome: FlowOutcome) {
110 match sync_lock::lock(&self.inner.events, "SupervisorLink::notify") {
111 Ok(mut events) => {
112 events.push_back(ChildExit { id, outcome });
113 self.inner.cvar.notify_one();
114 }
115 Err(e) => report_fault(e),
116 }
117 }
118}
119
120pub struct Supervisor {
135 inner: Arc<Inner>,
136 thread: Option<JoinHandle<()>>,
137}
138
139impl Supervisor {
140 pub fn new(spawner: RuntimeSpawner) -> Result<Self, SpawnError> {
141 Self::with_config(spawner, SupervisorConfig::default())
142 }
143
144 pub fn with_config(spawner: RuntimeSpawner, config: SupervisorConfig) -> Result<Self, SpawnError> {
148 let inner = Arc::new(Inner {
149 spawner,
150 config,
151 events: Mutex::new(VecDeque::new()),
152 cvar: Condvar::new(),
153 children: Mutex::new(HashMap::new()),
154 restart_times: Mutex::new(VecDeque::new()),
155 intensity_exceeded: AtomicBool::new(false),
156 shutdown: AtomicBool::new(false),
157 });
158 let drive_inner = inner.clone();
159 let thread = std::thread::Builder::new()
160 .name("byteflow-supervisor".into())
161 .spawn(move || drive(drive_inner))
162 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
163 Ok(Supervisor {
164 inner,
165 thread: Some(thread),
166 })
167 }
168
169 pub fn start_child(&self, spec: ChildSpec) -> Result<FlowHandle, SpawnError> {
173 spawn_child(&self.inner, spec)
174 }
175
176 pub fn live_children(&self) -> usize {
177 match sync_lock::lock(&self.inner.children, "Supervisor::live_children") {
178 Ok(g) => g.len(),
179 Err(e) => {
180 report_fault(e);
181 0
182 }
183 }
184 }
185
186 pub fn intensity_exceeded(&self) -> bool {
191 self.inner.intensity_exceeded.load(Ordering::Acquire)
192 }
193
194 pub fn shutdown(mut self) {
197 self.inner.shutdown.store(true, Ordering::Release);
198 self.inner.cvar.notify_all();
199 if let Some(t) = self.thread.take() {
200 let _ = t.join();
201 }
202 }
203}
204
205fn spawn_child(inner: &Arc<Inner>, spec: ChildSpec) -> Result<FlowHandle, SpawnError> {
206 let link = SupervisorLink {
207 inner: inner.clone(),
208 };
209 let mut children = match sync_lock::lock(&inner.children, "spawn_child") {
213 Ok(c) => c,
214 Err(e) => {
215 report_fault(e);
216 return Err(SpawnError::VmInit(
217 "supervisor child table poisoned".into(),
218 ));
219 }
220 };
221 let handle = inner.spawner.spawn_linked(
222 spec.function,
223 &spec.args,
224 spec.restart,
225 link,
226 )?;
227 children.insert(handle.id(), LiveChild { spec });
228 Ok(handle)
229}
230
231fn should_restart(policy: RestartPolicy, outcome: &FlowOutcome) -> bool {
232 match policy {
233 RestartPolicy::Always => true,
234 RestartPolicy::OnFailure => matches!(outcome, FlowOutcome::Failed(_)),
235 RestartPolicy::Never => false,
236 }
237}
238
239fn intensity_hit(inner: &Inner) -> bool {
240 let now = Instant::now();
241 let mut times = match sync_lock::lock(&inner.restart_times, "intensity_hit") {
242 Ok(t) => t,
243 Err(e) => {
244 report_fault(e);
245 return true;
246 }
247 };
248 times.push_back(now);
249 let window_start = now.checked_sub(inner.config.max_period).unwrap_or(now);
250 while times.front().map(|t| *t < window_start).unwrap_or(false) {
251 times.pop_front();
252 }
253 if times.len() as u32 > inner.config.max_restarts {
254 inner.intensity_exceeded.store(true, Ordering::Release);
255 true
256 } else {
257 false
258 }
259}
260
261fn drive(inner: Arc<Inner>) {
262 loop {
263 if inner.shutdown.load(Ordering::Acquire) {
264 return;
265 }
266 let exit = {
267 let mut events = match sync_lock::lock(&inner.events, "supervisor::drive") {
268 Ok(e) => e,
269 Err(e) => {
270 report_fault(e);
271 return;
272 }
273 };
274 loop {
275 if inner.shutdown.load(Ordering::Acquire) {
276 return;
277 }
278 if let Some(exit) = events.pop_front() {
279 break exit;
280 }
281 match sync_lock::wait_timeout(
282 &inner.cvar,
283 events,
284 Duration::from_millis(100),
285 "supervisor::wait",
286 ) {
287 Ok((guard, _)) => events = guard,
288 Err(e) => {
289 report_fault(e);
290 return;
291 }
292 }
293 }
294 };
295 handle_exit(&inner, exit);
296 }
297}
298
299fn handle_exit(inner: &Arc<Inner>, exit: ChildExit) {
300 let spec = {
301 let mut children = match sync_lock::lock(&inner.children, "handle_exit") {
302 Ok(c) => c,
303 Err(e) => {
304 report_fault(e);
305 return;
306 }
307 };
308 match children.remove(&exit.id) {
309 Some(live) => live.spec,
310 None => return,
311 }
312 };
313
314 if !should_restart(spec.restart, &exit.outcome) {
315 return;
316 }
317 if inner.intensity_exceeded.load(Ordering::Acquire) || intensity_hit(inner) {
318 return;
319 }
320
321 let _ = spawn_child(inner, spec);
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use std::time::{Duration, Instant};
328
329 use crate::bytecode::{Chunk, ChunkBuilder, Value};
330 use crate::scheduler::runtime::{Runtime, RuntimeConfig};
331
332 fn trap_chunk() -> Chunk {
333 let mut b = ChunkBuilder::new("trap");
334 b.begin_function("boom", 0, 1);
335 b.emit_trap(1);
336 b.finish()
337 }
338
339 fn ok_chunk() -> Chunk {
340 let mut b = ChunkBuilder::new("ok");
341 b.begin_function("main", 0, 1);
342 b.emit_load_imm(0, 7);
343 b.emit_return(0);
344 b.finish()
345 }
346
347 fn tiny_runtime(chunk: Chunk) -> Runtime {
348 Runtime::with_config(
349 chunk,
350 RuntimeConfig {
351 workers: 1,
352 quantum: 1_000,
353 },
354 )
355 .expect("runtime")
356 }
357
358 fn wait_until(mut pred: impl FnMut() -> bool) {
359 let start = Instant::now();
360 while !pred() {
361 assert!(
362 start.elapsed() < Duration::from_secs(2),
363 "supervisor test timed out"
364 );
365 std::thread::sleep(Duration::from_millis(5));
366 }
367 }
368
369 #[test]
370 fn on_failure_does_not_restart_a_clean_exit() {
371 let rt = tiny_runtime(ok_chunk());
372 let sup = Supervisor::new(rt.spawner()).expect("supervisor");
373 let outcome = sup
374 .start_child(ChildSpec::new("main", 0).restart(RestartPolicy::OnFailure))
375 .expect("start_child")
376 .join();
377 wait_until(|| sup.live_children() == 0);
378 let spawned = rt.metrics().processes_spawned;
379 sup.shutdown();
380 rt.shutdown();
381 assert!(matches!(outcome, FlowOutcome::Completed(_)));
382 assert_eq!(spawned, 1);
383 }
384
385 #[test]
386 fn on_failure_restarts_until_intensity() {
387 let rt = tiny_runtime(trap_chunk());
388 let sup = Supervisor::with_config(
389 rt.spawner(),
390 SupervisorConfig {
391 max_restarts: 2,
392 max_period: Duration::from_secs(5),
393 },
394 )
395 .expect("supervisor");
396 let _first = sup
397 .start_child(ChildSpec::new("boom", 0).restart(RestartPolicy::OnFailure))
398 .expect("start_child");
399 wait_until(|| sup.intensity_exceeded() && rt.metrics().processes_failed >= 3);
400 let spawned = rt.metrics().processes_spawned;
401 let failed = rt.metrics().processes_failed;
402 sup.shutdown();
403 rt.shutdown();
404 assert_eq!(spawned, 3);
406 assert_eq!(failed, 3);
407 }
408
409 #[test]
410 fn always_restarts_a_clean_exit_until_intensity() {
411 let rt = tiny_runtime(ok_chunk());
412 let sup = Supervisor::with_config(
413 rt.spawner(),
414 SupervisorConfig {
415 max_restarts: 2,
416 max_period: Duration::from_secs(5),
417 },
418 )
419 .expect("supervisor");
420 let _ = sup
421 .start_child(ChildSpec::new("main", 0).restart(RestartPolicy::Always))
422 .expect("start_child");
423 wait_until(|| sup.intensity_exceeded() && rt.metrics().processes_completed >= 3);
424 let spawned = rt.metrics().processes_spawned;
425 sup.shutdown();
426 rt.shutdown();
427 assert_eq!(spawned, 3);
428 }
429
430 #[test]
431 fn policy_table() {
432 let ok = FlowOutcome::Completed(Value::Unit);
433 let fail = FlowOutcome::Failed("boom".into());
434 assert!(should_restart(RestartPolicy::Always, &ok));
435 assert!(should_restart(RestartPolicy::Always, &fail));
436 assert!(!should_restart(RestartPolicy::OnFailure, &ok));
437 assert!(should_restart(RestartPolicy::OnFailure, &fail));
438 assert!(!should_restart(RestartPolicy::Never, &ok));
439 assert!(!should_restart(RestartPolicy::Never, &fail));
440 }
441}