use crate::detached_finalize::{
END_TIME_SOURCE_DOCKER_FINISHED_AT, END_TIME_SOURCE_LOG_FOOTER, END_TIME_SOURCE_OBSERVED_AT,
};
use crate::docker_cleanup::docker_command;
use crate::docker_post_mortem::normalize_docker_timestamp;
use crate::execution_store::ExecutionRecord;
use crate::status_footer::LogFooter;
use chrono::Utc;
use std::process::Command;
#[derive(Clone, Default)]
pub struct DockerState {
pub running: bool,
exit_code: Option<i32>,
oom_killed: Option<bool>,
started_at: Option<String>,
finished_at: Option<String>,
}
pub(crate) fn inspect_docker_state(session_name: &str) -> Option<DockerState> {
let output = Command::new(docker_command())
.args([
"inspect",
"-f",
concat!(
"{{.State.Running}} {{.State.ExitCode}} {{.State.OOMKilled}} ",
"{{.State.StartedAt}} {{.State.FinishedAt}}"
),
session_name,
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let trimmed = stdout.trim();
if trimmed.is_empty() {
return None;
}
let mut parts = trimmed.split_whitespace();
let running = parts.next() == Some("true");
let exit_code = parts.next().and_then(|value| value.parse::<i32>().ok());
let oom_killed = parts.next().and_then(|value| match value {
"true" => Some(true),
"false" => Some(false),
_ => None,
});
let started_at = normalize_docker_timestamp(parts.next());
let finished_at = normalize_docker_timestamp(parts.next());
Some(DockerState {
running,
exit_code,
oom_killed,
started_at,
finished_at,
})
}
pub(crate) fn is_detached_docker_record(record: &ExecutionRecord) -> bool {
record.options.get("isolated").and_then(|v| v.as_str()) == Some("docker")
&& record.options.get("isolationMode").and_then(|v| v.as_str()) == Some("detached")
&& record
.options
.get("sessionName")
.and_then(|v| v.as_str())
.is_some()
}
pub(crate) fn read_docker_state(record: &ExecutionRecord) -> Option<DockerState> {
if record.options.get("isolated")?.as_str()? != "docker" {
return None;
}
let session_name = record.options.get("sessionName")?.as_str()?;
inspect_docker_state(session_name)
}
pub(crate) fn backend_exit_code(docker_state: Option<&DockerState>) -> Option<i32> {
let state = docker_state?;
if state.running {
None
} else {
state.exit_code
}
}
pub(crate) fn resolve_oom_observation(
record: &ExecutionRecord,
docker_state: Option<&DockerState>,
) -> Option<bool> {
let inspected = docker_state.and_then(|state| state.oom_killed);
if record.oom_killed == Some(true) || inspected == Some(true) {
return Some(true);
}
if record.oom_killed == Some(false) || inspected == Some(false) {
return Some(false);
}
None
}
pub fn is_detached_session_alive(record: &ExecutionRecord) -> Option<bool> {
let session_name = record.options.get("sessionName")?.as_str()?;
let isolation_mode = record.options.get("isolationMode")?.as_str()?;
let isolated = record.options.get("isolated")?.as_str()?;
if isolation_mode != "detached" {
return None;
}
match isolated {
"screen" => {
let output = Command::new("screen").args(["-ls"]).output().ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
Some(stdout.contains(session_name))
}
"tmux" => {
let status = Command::new("tmux")
.args(["has-session", "-t", session_name])
.output()
.ok()?;
Some(status.status.success())
}
"docker" => {
inspect_docker_state(session_name).map(|state| state.running)
}
"ssh" => {
#[cfg(unix)]
{
if let Some(pid) = record.pid {
let result = unsafe { libc::kill(pid as i32, 0) };
Some(result == 0)
} else {
None
}
}
#[cfg(not(unix))]
{
let _ = record.pid;
None
}
}
_ => None,
}
}
pub(crate) fn apply_end_time(
record: &mut ExecutionRecord,
docker_state: Option<&DockerState>,
footer: &LogFooter,
) {
if let Some(started_at) = docker_state.and_then(|state| state.started_at.clone()) {
record.container_started_at = Some(started_at);
}
if record.end_time.is_some() {
return;
}
if let Some(finished_at) = docker_state.and_then(|state| state.finished_at.clone()) {
record.end_time = Some(finished_at);
record.end_time_source = Some(END_TIME_SOURCE_DOCKER_FINISHED_AT.to_string());
return;
}
if let Some(finished_at) = footer.finished_at.clone() {
record.end_time = Some(finished_at);
record.end_time_source = Some(END_TIME_SOURCE_LOG_FOOTER.to_string());
return;
}
let now = Utc::now().to_rfc3339();
record.end_time = Some(now.clone());
record.end_time_source = Some(END_TIME_SOURCE_OBSERVED_AT.to_string());
record.observed_at = Some(now);
}