Skip to main content

magic_bird/store/
pending.rs

1//! Runner liveness checking and recovery statistics.
2//!
3//! This module provides utilities for:
4//! - Checking if a process/runner is still alive
5//! - Tracking recovery operation statistics
6//!
7//! **V5 Schema Note**: In v5, pending detection is done via the invocations VIEW
8//! (attempts without matching outcomes). The old pending file mechanism from v4
9//! has been removed.
10
11/// Check if a runner is still alive based on its runner_id.
12///
13/// Supports various runner ID formats:
14/// - `pid:12345` - Local process ID
15/// - `gha:run:12345678` - GitHub Actions run
16/// - `k8s:pod:abc123` - Kubernetes pod
17pub fn is_runner_alive(runner_id: &str) -> bool {
18    if let Some(pid_str) = runner_id.strip_prefix("pid:") {
19        // Local process - check if PID exists
20        if let Ok(pid) = pid_str.parse::<i32>() {
21            return is_pid_alive(pid);
22        }
23    } else if runner_id.starts_with("gha:") {
24        // GitHub Actions - we can't reliably check, assume dead after max_age
25        // TODO: Could use GitHub API to check run status
26        return false;
27    } else if runner_id.starts_with("k8s:") {
28        // Kubernetes - we can't reliably check, assume dead after max_age
29        // TODO: Could use kubectl to check pod status
30        return false;
31    }
32
33    // Unknown format - assume dead
34    false
35}
36
37/// Check if a local PID is still alive.
38#[cfg(unix)]
39fn is_pid_alive(pid: i32) -> bool {
40    // Send signal 0 to check if process exists
41    // This works even for processes owned by other users
42    unsafe { libc::kill(pid, 0) == 0 }
43}
44
45#[cfg(not(unix))]
46fn is_pid_alive(_pid: i32) -> bool {
47    // On non-Unix, we can't easily check - assume alive
48    true
49}
50
51/// Statistics from recovery operations.
52#[derive(Debug, Default, Clone)]
53pub struct RecoveryStats {
54    /// Number of pending attempts checked.
55    pub pending_checked: usize,
56
57    /// Number still running (runner alive).
58    pub still_running: usize,
59
60    /// Number marked as orphaned.
61    pub orphaned: usize,
62
63    /// Number of errors encountered.
64    pub errors: usize,
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_is_runner_alive_current_process() {
73        let pid = std::process::id() as i32;
74        let runner_id = format!("pid:{}", pid);
75        assert!(is_runner_alive(&runner_id));
76    }
77
78    #[test]
79    fn test_is_runner_alive_dead_process() {
80        // PID 999999999 likely doesn't exist
81        let runner_id = "pid:999999999";
82        assert!(!is_runner_alive(runner_id));
83    }
84
85    #[test]
86    fn test_is_runner_alive_unknown_format() {
87        assert!(!is_runner_alive("unknown:123"));
88        assert!(!is_runner_alive("gha:run:12345"));
89        assert!(!is_runner_alive("k8s:pod:abc123"));
90    }
91}