1use crate::child_runner::runner::{ChildRunHandle, ChildRunReport, wait_for_report};
8use crate::control::outcome::{
9 ChildAttemptStatus, ChildControlFailure, ChildControlOperation, ChildLivenessState,
10 ChildRuntimeRecord, ChildStopState, GenerationFenceState, RestartLimitState,
11};
12use crate::error::types::SupervisorError;
13use crate::id::types::{ChildId, ChildStartCount, Generation, SupervisorPath};
14use crate::readiness::signal::ReadinessState;
15use serde::Serialize;
16use std::collections::VecDeque;
17use std::fmt::{Display, Formatter};
18use std::time::{Duration, SystemTime, UNIX_EPOCH};
19use tokio::sync::watch;
20use tokio::task::AbortHandle;
21use tokio::time::Instant;
22use tokio_util::sync::CancellationToken;
23
24pub const DEFAULT_HEARTBEAT_TIMEOUT_SECS: u64 = 5;
30
31#[derive(Debug, Clone, Default)]
33pub struct RestartLimitTracker {
34 failure_timestamps: VecDeque<u128>,
36}
37
38impl RestartLimitTracker {
39 pub fn new() -> Self {
41 Self::default()
42 }
43
44 pub fn refresh(&mut self, now_unix_nanos: u128, window: Duration, count_failure: bool) -> u32 {
56 self.prune(now_unix_nanos, window);
57 if count_failure {
58 self.failure_timestamps.push_back(now_unix_nanos);
59 }
60 self.failure_timestamps.len().min(u32::MAX as usize) as u32
61 }
62
63 fn prune(&mut self, now_unix_nanos: u128, window: Duration) {
65 let window_nanos = window.as_nanos();
66 while self
67 .failure_timestamps
68 .front()
69 .is_some_and(|timestamp| now_unix_nanos.saturating_sub(*timestamp) > window_nanos)
70 {
71 self.failure_timestamps.pop_front();
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy)]
78pub struct RuntimeTimeBase {
79 pub base_instant: Instant,
81 pub base_unix_nanos: u128,
83}
84
85impl RuntimeTimeBase {
86 pub fn new() -> Self {
88 Self {
89 base_instant: Instant::now(),
90 base_unix_nanos: SystemTime::now()
91 .duration_since(UNIX_EPOCH)
92 .map_or(0, |duration| duration.as_nanos()),
93 }
94 }
95
96 pub fn now_unix_nanos(&self) -> u128 {
98 self.instant_to_unix_nanos(Instant::now())
99 }
100
101 pub fn instant_to_unix_nanos(&self, instant: Instant) -> u128 {
107 if instant >= self.base_instant {
108 self.base_unix_nanos
109 .saturating_add(instant.duration_since(self.base_instant).as_nanos())
110 } else {
111 self.base_unix_nanos
112 .saturating_sub(self.base_instant.duration_since(instant).as_nanos())
113 }
114 }
115}
116
117impl Default for RuntimeTimeBase {
118 fn default() -> Self {
120 Self::new()
121 }
122}
123
124#[derive(Debug, Clone, Serialize)]
130pub struct ChildExitSummary {
131 pub exit_code: Option<i32>,
133 pub exit_reason: String,
135 pub exited_at_unix_nanos: u128,
137}
138
139impl ChildExitSummary {
140 pub fn from_report(report: &ChildRunReport, exited_at_unix_nanos: u128) -> Self {
151 let exit_reason = match &report.exit {
152 crate::child_runner::run_exit::TaskExit::Succeeded => "succeeded".to_owned(),
153 crate::child_runner::run_exit::TaskExit::Cancelled => "cancelled".to_owned(),
154 crate::child_runner::run_exit::TaskExit::Failed(f) => f.message.clone(),
155 crate::child_runner::run_exit::TaskExit::Panicked(msg) => format!("panicked: {msg}"),
156 crate::child_runner::run_exit::TaskExit::TimedOut => "timed out".to_owned(),
157 };
158 Self {
159 exit_code: None,
160 exit_reason,
161 exited_at_unix_nanos,
162 }
163 }
164}
165
166impl Display for ChildExitSummary {
167 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
169 match self.exit_code {
170 Some(code) => write!(formatter, "code={} reason={}", code, self.exit_reason),
171 None => write!(formatter, "reason={}", self.exit_reason),
172 }
173 }
174}
175
176#[derive(Debug, Serialize)]
186pub struct ChildSlot {
187 pub child_id: ChildId,
189 pub path: SupervisorPath,
191 pub status: ChildAttemptStatus,
193 pub operation: ChildControlOperation,
195 pub generation: Option<Generation>,
197 pub attempt: Option<ChildStartCount>,
199 pub restart_count: u64,
201 #[serde(skip)]
203 pub cancellation_token: Option<CancellationToken>,
204 #[serde(skip)]
206 pub abort_handle: Option<AbortHandle>,
207 #[serde(skip)]
209 pub completion_receiver:
210 Option<watch::Receiver<Option<Result<ChildRunReport, SupervisorError>>>>,
211 #[serde(skip)]
213 pub heartbeat_receiver: Option<watch::Receiver<Option<Instant>>>,
214 #[serde(skip)]
216 pub readiness_receiver: Option<watch::Receiver<ReadinessState>>,
217 pub last_exit: Option<ChildExitSummary>,
219 pub last_ready_at: Option<u128>,
221 pub last_heartbeat_at: Option<u128>,
223 pub restart_window: Duration,
225 pub pending_restart: bool,
227 pub attempt_cancel_delivered: bool,
229 pub abort_requested: bool,
231 #[serde(skip)]
234 pub restart_limit: RestartLimitState,
235 #[serde(skip)]
237 pub restart_limit_tracker: RestartLimitTracker,
238 pub stop_state: ChildStopState,
240 pub stop_deadline_at_unix_nanos: Option<u128>,
242 pub last_control_failure: Option<ChildControlFailure>,
244 pub stale_event_attempt: Option<ChildStartCount>,
246 #[serde(skip)]
248 pub generation_fence: GenerationFenceState,
249 #[serde(skip)]
251 pub registry_identity_anchor_for_spawn_attempt: Option<(Generation, ChildStartCount, u64)>,
252 #[serde(skip)]
254 pub last_observed_readiness: ReadinessState,
255 #[serde(skip)]
257 pub stale_after: Duration,
258 #[serde(skip)]
260 pub group: Option<String>,
261 #[serde(skip)]
265 pub orphaned: bool,
266}
267
268impl ChildSlot {
269 pub fn new(child_id: ChildId, path: SupervisorPath, restart_window: Duration) -> Self {
281 Self {
282 child_id,
283 path,
284 status: ChildAttemptStatus::Stopped,
285 operation: ChildControlOperation::Active,
286 generation: None,
287 attempt: None,
288 restart_count: 0,
289 cancellation_token: None,
290 abort_handle: None,
291 completion_receiver: None,
292 heartbeat_receiver: None,
293 readiness_receiver: None,
294 last_exit: None,
295 last_ready_at: None,
296 last_heartbeat_at: None,
297 restart_window,
298 pending_restart: false,
299 attempt_cancel_delivered: false,
300 abort_requested: false,
301 restart_limit: RestartLimitState::default(),
302 restart_limit_tracker: RestartLimitTracker::new(),
303 stop_state: ChildStopState::NoActiveAttempt,
304 stop_deadline_at_unix_nanos: None,
305 last_control_failure: None,
306 stale_event_attempt: None,
307 generation_fence: GenerationFenceState::placeholder(),
308 registry_identity_anchor_for_spawn_attempt: None,
309 last_observed_readiness: ReadinessState::Unreported,
310 stale_after: Duration::from_secs(DEFAULT_HEARTBEAT_TIMEOUT_SECS),
311 group: None,
312 orphaned: false,
313 }
314 }
315
316 pub fn new_placeholder(child_id: ChildId, path: SupervisorPath) -> Self {
330 Self::new(child_id, path, Duration::from_secs(60))
331 }
332
333 pub fn activate(
346 &mut self,
347 generation: Generation,
348 attempt: ChildStartCount,
349 status: ChildAttemptStatus,
350 handle: ChildRunHandle,
351 ) {
352 self.generation = Some(generation);
353 self.attempt = Some(attempt);
354 self.status = status;
355 self.generation_fence.active_generation = Some(generation);
356 self.generation_fence.active_attempt = Some(attempt);
357 self.cancellation_token = Some(handle.cancellation_token);
358 self.abort_handle = Some(handle.abort_handle);
359 self.completion_receiver = Some(handle.completion_receiver);
360 self.heartbeat_receiver = Some(handle.heartbeat_receiver);
361 self.readiness_receiver = Some(handle.readiness_receiver);
362 self.last_exit = None;
363 self.last_ready_at = None;
364 self.last_heartbeat_at = None;
365 self.last_observed_readiness = ReadinessState::Unreported;
366 self.attempt_cancel_delivered = false;
367 self.abort_requested = false;
368 self.pending_restart = false;
369 self.stop_state = ChildStopState::Idle;
370 self.stop_deadline_at_unix_nanos = None;
371 self.last_control_failure = None;
372 self.stale_event_attempt = None;
373 self.registry_identity_anchor_for_spawn_attempt = None;
374 self.generation_fence.phase = GenerationFenceState::placeholder().phase;
375 }
376
377 pub fn deactivate(&mut self, exit_summary: ChildExitSummary) {
390 self.last_exit = Some(exit_summary);
391 self.restart_count = self.restart_count.saturating_add(1);
392 self.generation = None;
393 self.attempt = None;
394 self.status = ChildAttemptStatus::Stopped;
395 self.cancellation_token = None;
396 self.abort_handle = None;
397 self.completion_receiver = None;
398 self.heartbeat_receiver = None;
399 self.readiness_receiver = None;
400 self.last_ready_at = None;
401 self.last_heartbeat_at = None;
402 self.attempt_cancel_delivered = false;
403 self.abort_requested = false;
404 self.pending_restart = false;
405 self.generation_fence.active_generation = None;
406 self.generation_fence.active_attempt = None;
407 self.stop_state = ChildStopState::NoActiveAttempt;
408 self.stop_deadline_at_unix_nanos = None;
409 self.stale_event_attempt = None;
410 self.registry_identity_anchor_for_spawn_attempt = None;
411 }
412
413 pub fn clear_instance(&mut self) {
415 self.generation = None;
416 self.attempt = None;
417 self.status = ChildAttemptStatus::Stopped;
418 self.generation_fence.active_generation = None;
419 self.generation_fence.active_attempt = None;
420 self.cancellation_token = None;
421 self.abort_handle = None;
422 self.completion_receiver = None;
423 self.heartbeat_receiver = None;
424 self.readiness_receiver = None;
425 self.attempt_cancel_delivered = false;
426 self.abort_requested = false;
427 self.stop_deadline_at_unix_nanos = None;
428 self.stale_event_attempt = None;
429 self.registry_identity_anchor_for_spawn_attempt = None;
430 self.stop_state = ChildStopState::NoActiveAttempt;
431 }
432
433 pub fn has_active_attempt(&self) -> bool {
443 self.attempt.is_some() && self.cancellation_token.is_some()
444 }
445
446 pub fn cancel(&mut self) -> bool {
456 let Some(token) = &self.cancellation_token else {
457 return false;
458 };
459 if self.attempt_cancel_delivered {
460 return false;
461 }
462 token.cancel();
463 self.attempt_cancel_delivered = true;
464 self.status = ChildAttemptStatus::Cancelling;
465 true
466 }
467
468 pub fn abort(&mut self) -> bool {
478 let Some(handle) = &self.abort_handle else {
479 return false;
480 };
481 if self.abort_requested {
482 return false;
483 }
484 handle.abort();
485 self.abort_requested = true;
486 true
487 }
488
489 pub async fn wait_for_report(&mut self) -> Result<ChildRunReport, SupervisorError> {
499 let Some(receiver) = &mut self.completion_receiver else {
500 return Err(SupervisorError::InvalidTransition {
501 message: "child slot has no active completion receiver".to_owned(),
502 });
503 };
504 wait_for_report(receiver).await
505 }
506
507 pub fn observe_liveness(
517 &mut self,
518 now_unix_nanos: u128,
519 time_base: &RuntimeTimeBase,
520 ) -> ChildLivenessState {
521 if let Some(receiver) = &self.heartbeat_receiver {
522 let heartbeat = *receiver.borrow();
523 if let Some(instant) = heartbeat {
524 self.last_heartbeat_at = Some(time_base.instant_to_unix_nanos(instant));
529 }
530 }
531 let readiness = if let Some(receiver) = &self.readiness_receiver {
532 let r = *receiver.borrow();
533 if r == ReadinessState::Ready {
534 self.last_ready_at = Some(now_unix_nanos);
535 }
536 r
537 } else {
538 ReadinessState::Unreported
539 };
540 let stale_after = self.stale_after;
541 let heartbeat_stale = self.last_heartbeat_at.is_some_and(|heartbeat| {
542 let elapsed_nanos = now_unix_nanos.saturating_sub(heartbeat);
543 elapsed_nanos >= stale_after.as_nanos()
544 });
545 ChildLivenessState::new(self.last_heartbeat_at, heartbeat_stale, readiness)
546 }
547
548 pub fn update_restart_limit(
562 &mut self,
563 window: Duration,
564 limit: u32,
565 used: u32,
566 time_base: &RuntimeTimeBase,
567 ) -> RestartLimitState {
568 let mut updated_at = time_base.now_unix_nanos();
569 if updated_at <= self.restart_limit.updated_at_unix_nanos {
570 updated_at = self.restart_limit.updated_at_unix_nanos.saturating_add(1);
571 }
572 self.restart_limit = RestartLimitState {
573 window,
574 limit,
575 used,
576 remaining: limit.saturating_sub(used),
577 exhausted: used >= limit,
578 updated_at_unix_nanos: updated_at,
579 };
580 self.restart_limit.clone()
581 }
582
583 pub fn refresh_restart_limit(
585 &mut self,
586 window: Duration,
587 limit: u32,
588 count_failure: bool,
589 time_base: &RuntimeTimeBase,
590 ) -> RestartLimitState {
591 let now = time_base.now_unix_nanos();
592 let used = self
593 .restart_limit_tracker
594 .refresh(now, window, count_failure);
595 self.update_restart_limit(window, limit, used, time_base)
596 }
597
598 pub fn to_record(&self, liveness: ChildLivenessState) -> ChildRuntimeRecord {
600 let status = if self.attempt.is_some() {
602 Some(self.status)
603 } else {
604 None
605 };
606 let pending_restart = self
607 .generation_fence
608 .pending_restart
609 .as_ref()
610 .map(crate::control::outcome::PendingRestartSummary::from);
611 ChildRuntimeRecord::new(
612 self.child_id.clone(),
613 self.path.clone(),
614 self.generation,
615 self.attempt,
616 status,
617 self.operation,
618 liveness,
619 self.restart_limit.clone(),
620 self.stop_state,
621 self.last_control_failure.clone(),
622 self.generation_fence.phase,
623 pending_restart,
624 )
625 }
626}