Skip to main content

start_command/
launch_owner.rs

1//! Reservations survive a launcher crash without permanently blocking resume.
2use crate::{execution_store::ExecutionRecord, local_hostname};
3use serde_json::{json, Value};
4
5pub fn mark_launch(record: &mut ExecutionRecord) {
6    record.options.insert("launchPending".into(), json!(true));
7    record.options.insert(
8        "launchOwner".into(),
9        json!({"pid":std::process::id(), "hostname":hostname()}),
10    );
11}
12
13pub fn has_active_launch(record: &ExecutionRecord) -> bool {
14    if record.options.get("launchPending").and_then(Value::as_bool) != Some(true) {
15        return false;
16    }
17    let Some(owner) = record.options.get("launchOwner") else {
18        return true;
19    };
20    if owner["hostname"].as_str() != Some(&hostname()) {
21        return true;
22    }
23    owner["pid"].as_u64().is_none_or(process_alive)
24}
25
26pub fn hostname() -> String {
27    local_hostname::get()
28        .map(|s| s.to_string_lossy().into())
29        .unwrap_or_default()
30}
31
32pub fn process_alive(pid: u64) -> bool {
33    if pid == 0 || pid > i32::MAX as u64 {
34        return true;
35    }
36    #[cfg(unix)]
37    {
38        (unsafe { libc::kill(pid as i32, 0) }) == 0
39            || std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
40    }
41    #[cfg(windows)]
42    {
43        let output = std::process::Command::new("tasklist")
44            .args(["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"])
45            .output();
46        match output {
47            Ok(output) if output.status.success() => {
48                String::from_utf8_lossy(&output.stdout).contains(&format!("\"{}\"", pid))
49            }
50            _ => true,
51        }
52    }
53    #[cfg(not(any(unix, windows)))]
54    {
55        true
56    }
57}