taskvisor 0.7.0

In-process Tokio task supervisor with retries, reliable outcomes, and per-key queue/replace/reject admission
Documentation
//! Pull-side controller introspection.
//!
//! This module builds [`ControllerSnapshot`] from internal slot state.
//! It is used by `SupervisorHandle::controller_snapshot`.

use std::sync::Arc;
use std::time::Duration;

use crate::controller::slot::SlotPhase;
use crate::controller::view::{ControllerSnapshot, SlotStatusKind, SlotView};

use super::Controller;

impl Controller {
    /// Builds a best-effort rolling snapshot of tracked slots.
    ///
    /// The controller captures slot keys, looks each key up again, and then locks each surviving slot separately.
    /// A new slot created after key capture is absent.
    ///
    /// A removed slot may still appear if its `Arc` was cloned before removal.
    /// Each included `SlotView` is internally consistent, but the full collection is not globally atomic.
    ///
    /// The result is sorted by slot key for stable output in tests, logs, and dashboards.
    pub(crate) async fn snapshot(&self) -> ControllerSnapshot {
        let keys: Vec<Arc<str>> = self.slots.iter().map(|e| Arc::clone(e.key())).collect();

        let mut slots = Vec::with_capacity(keys.len());
        for key in keys {
            let Some(slot_arc) = self.slots.get(&*key).map(|e| e.clone()) else {
                continue;
            };

            let slot = slot_arc.lock().await;
            let phase = slot.phase();
            let (status, status_for) = match phase {
                SlotPhase::Idle => (SlotStatusKind::Idle, Duration::ZERO),
                SlotPhase::Admitting { since, .. } => (SlotStatusKind::Admitting, since.elapsed()),
                SlotPhase::Running { started_at, .. } => {
                    (SlotStatusKind::Running, started_at.elapsed())
                }
                SlotPhase::CancelPendingAdmission { requested_at, .. }
                | SlotPhase::Terminating { requested_at, .. } => {
                    (SlotStatusKind::Terminating, requested_at.elapsed())
                }
            };

            slots.push(SlotView {
                slot: Arc::clone(&key),
                status,
                owner_id: phase.owner_id(),
                queue_depth: slot.queue.len(),
                status_for,
            });
        }

        slots.sort_by(|a, b| a.slot.cmp(&b.slot));
        ControllerSnapshot { slots }
    }
}