laburnum 1.17.0

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! Cross-platform process liveness detection.
//!
//! Uses the `sysinfo` crate for safe, cross-platform process information.

use sysinfo::{
  Pid,
  ProcessRefreshKind,
  ProcessesToUpdate,
  System,
};

/// Information about a process, used for liveness verification.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProcessInfo {
  pub pid:        u32,
  /// Process start time in seconds since UNIX epoch.
  pub start_time: u64,
}

impl ProcessInfo {
  /// Get information about the current process.
  pub fn current() -> Self {
    let pid = std::process::id();
    let sysinfo_pid = Pid::from_u32(pid);
    let mut system = System::new();
    system.refresh_processes_specifics(
      ProcessesToUpdate::Some(&[sysinfo_pid]),
      false,
      ProcessRefreshKind::nothing(),
    );

    let start_time = system
      .process(sysinfo_pid)
      .map(|p| p.start_time())
      .unwrap_or(0);

    Self { pid, start_time }
  }

  /// Check if a process with the given PID is alive.
  ///
  /// If `expected_start_time` is provided, also verifies that the process
  /// start time matches to detect PID reuse.
  pub fn is_alive(pid: u32, expected_start_time: Option<u64>) -> bool {
    let sysinfo_pid = Pid::from_u32(pid);
    let mut system = System::new();
    system.refresh_processes_specifics(
      ProcessesToUpdate::Some(&[sysinfo_pid]),
      false,
      ProcessRefreshKind::nothing(),
    );

    match system.process(sysinfo_pid) {
      | Some(process) => {
        if let Some(expected) = expected_start_time {
          // Verify start time matches to detect PID reuse
          process.start_time() == expected
        } else {
          true
        }
      },
      | None => false,
    }
  }

  /// Check if this process is still alive.
  pub fn is_self_alive(&self) -> bool {
    Self::is_alive(self.pid, Some(self.start_time))
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn current_process_has_valid_pid() {
    let info = ProcessInfo::current();
    assert_eq!(info.pid, std::process::id());
  }

  #[test]
  fn current_process_has_nonzero_start_time() {
    let info = ProcessInfo::current();
    // Start time should be a reasonable UNIX timestamp (after year 2020)
    assert!(
      info.start_time > 1_577_836_800,
      "start_time {} should be after 2020",
      info.start_time
    );
  }

  #[test]
  fn current_process_is_alive() {
    let pid = std::process::id();
    assert!(
      ProcessInfo::is_alive(pid, None),
      "current process should be alive"
    );
  }

  #[test]
  fn current_process_is_alive_with_correct_start_time() {
    let info = ProcessInfo::current();
    assert!(
      ProcessInfo::is_alive(info.pid, Some(info.start_time)),
      "current process should be alive with matching start time"
    );
  }

  #[test]
  fn current_process_not_alive_with_wrong_start_time() {
    let info = ProcessInfo::current();
    // Use a start time from the past - this should fail the PID reuse check
    let wrong_start_time = info.start_time.saturating_sub(1000);
    assert!(
      !ProcessInfo::is_alive(info.pid, Some(wrong_start_time)),
      "process should not match with wrong start time (PID reuse detection)"
    );
  }

  #[test]
  fn nonexistent_process_is_not_alive() {
    // PID 0 is the kernel/idle process, but we can't signal it
    // Use a very high PID that's unlikely to exist
    let unlikely_pid = u32::MAX - 1;
    assert!(
      !ProcessInfo::is_alive(unlikely_pid, None),
      "nonexistent PID {} should not be alive",
      unlikely_pid
    );
  }

  #[test]
  fn is_self_alive_returns_true() {
    let info = ProcessInfo::current();
    assert!(info.is_self_alive(), "is_self_alive should return true");
  }

  #[test]
  fn process_info_is_copy() {
    let info = ProcessInfo::current();
    let copy = info;
    assert_eq!(info.pid, copy.pid);
    assert_eq!(info.start_time, copy.start_time);
  }

  #[test]
  fn init_process_is_alive() {
    // PID 1 (init/launchd/systemd) should always be running
    // Note: On some systems we may not have permission to query it,
    // so we just check that the call doesn't panic
    let _ = ProcessInfo::is_alive(1, None);
  }

  #[test]
  fn multiple_calls_return_consistent_results() {
    let info1 = ProcessInfo::current();
    let info2 = ProcessInfo::current();

    assert_eq!(info1.pid, info2.pid, "PID should be consistent");
    assert_eq!(
      info1.start_time, info2.start_time,
      "start_time should be consistent"
    );
  }
}