detached-shell 0.1.4

Noras.tech's minimalist detachable shell solution · zero configuration · not a complex multiplexer, just persistent sessions
Documentation
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// Health monitor for PTY sessions
/// Tracks session health and attempts recovery when issues are detected
pub struct HealthMonitor {
    last_activity: Arc<AtomicU64>,
    is_healthy: Arc<AtomicBool>,
    monitoring: Arc<AtomicBool>,
}

impl HealthMonitor {
    pub fn new() -> Self {
        Self {
            last_activity: Arc::new(AtomicU64::new(Self::now_as_secs())),
            is_healthy: Arc::new(AtomicBool::new(true)),
            monitoring: Arc::new(AtomicBool::new(false)),
        }
    }

    /// Start monitoring in a separate thread
    pub fn start_monitoring(&self, timeout_secs: u64) -> thread::JoinHandle<()> {
        let last_activity = Arc::clone(&self.last_activity);
        let is_healthy = Arc::clone(&self.is_healthy);
        let monitoring = Arc::clone(&self.monitoring);

        monitoring.store(true, Ordering::SeqCst);

        thread::spawn(move || {
            while monitoring.load(Ordering::SeqCst) {
                let last = last_activity.load(Ordering::SeqCst);
                let now = Self::now_as_secs();

                if now - last > timeout_secs {
                    // No activity for too long, mark as unhealthy
                    is_healthy.store(false, Ordering::SeqCst);
                } else {
                    is_healthy.store(true, Ordering::SeqCst);
                }

                thread::sleep(Duration::from_secs(1));
            }
        })
    }

    /// Update last activity timestamp
    pub fn update_activity(&self) {
        self.last_activity
            .store(Self::now_as_secs(), Ordering::SeqCst);
    }

    /// Check if the session is healthy
    pub fn is_healthy(&self) -> bool {
        self.is_healthy.load(Ordering::SeqCst)
    }

    /// Stop monitoring
    pub fn stop_monitoring(&self) {
        self.monitoring.store(false, Ordering::SeqCst);
    }

    /// Get current time as seconds since UNIX epoch
    fn now_as_secs() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or(Duration::ZERO)
            .as_secs()
    }
}

/// Session recovery strategies
#[allow(dead_code)]
pub enum RecoveryStrategy {
    /// Send terminal refresh sequences
    RefreshTerminal,
    /// Clear and reset buffers
    ResetBuffers,
    /// Restart PTY process
    #[allow(dead_code)]
    RestartProcess,
}

/// Attempt to recover a session
pub fn attempt_recovery(strategy: RecoveryStrategy, master_fd: i32) -> Result<(), String> {
    match strategy {
        RecoveryStrategy::RefreshTerminal => {
            // Send Ctrl+L to refresh
            unsafe {
                let refresh = b"\x0c";
                if libc::write(
                    master_fd,
                    refresh.as_ptr() as *const libc::c_void,
                    refresh.len(),
                ) < 0
                {
                    return Err("Failed to send refresh".to_string());
                }
            }
            Ok(())
        }
        RecoveryStrategy::ResetBuffers => {
            // Flush any pending I/O
            unsafe {
                let _ = libc::tcflush(master_fd, libc::TCIOFLUSH);
            }
            Ok(())
        }
        RecoveryStrategy::RestartProcess => {
            // This would require more complex logic to restart the shell process
            // For now, just return an error
            Err("Process restart not implemented".to_string())
        }
    }
}