Skip to main content

agent_exec/
wait.rs

1//! Implementation of the `wait` sub-command.
2//!
3//! Polls `state.json` until the job leaves the `running` state or a timeout
4//! is reached.
5
6use anyhow::Result;
7use tracing::debug;
8
9use crate::jobstore::{JobDir, resolve_root};
10use crate::schema::{Response, WaitData};
11
12/// Options for the `wait` sub-command.
13#[derive(Debug)]
14pub struct WaitOpts<'a> {
15    pub job_id: &'a str,
16    pub root: Option<&'a str>,
17    /// Poll interval in seconds.
18    pub poll_seconds: u64,
19    /// Total timeout in seconds (default 30).
20    /// Ignored when `forever` is true.
21    pub until_seconds: u64,
22    /// Wait indefinitely when true.
23    pub forever: bool,
24}
25
26impl<'a> Default for WaitOpts<'a> {
27    fn default() -> Self {
28        WaitOpts {
29            job_id: "",
30            root: None,
31            poll_seconds: 1,
32            until_seconds: 30,
33            forever: false,
34        }
35    }
36}
37
38fn log_file_size(path: &std::path::Path) -> Option<u64> {
39    std::fs::metadata(path).ok().map(|m| m.len())
40}
41
42pub fn build_wait_data(job_dir: &JobDir, state: &crate::schema::JobState) -> WaitData {
43    let stdout_total_bytes = log_file_size(&job_dir.stdout_path());
44    let stderr_total_bytes = log_file_size(&job_dir.stderr_path());
45    let updated_at = Some(state.updated_at.clone());
46
47    WaitData {
48        job_id: job_dir.job_id.clone(),
49        state: state.status().as_str().to_string(),
50        exit_code: state.exit_code(),
51        stdout_total_bytes,
52        stderr_total_bytes,
53        updated_at,
54    }
55}
56
57/// Execute `wait`: poll until done, then emit JSON.
58pub fn execute(opts: WaitOpts) -> Result<()> {
59    let root = resolve_root(opts.root);
60    let job_dir = JobDir::open(&root, opts.job_id)?;
61
62    let poll = std::time::Duration::from_secs(opts.poll_seconds.max(1));
63    let deadline = if opts.forever {
64        None
65    } else {
66        Some(std::time::Instant::now() + std::time::Duration::from_secs(opts.until_seconds))
67    };
68
69    loop {
70        let state = job_dir.read_state()?;
71        debug!(job_id = %opts.job_id, state = ?state.status(), "wait poll");
72
73        if !state.status().is_non_terminal() {
74            let response = Response::new("wait", build_wait_data(&job_dir, &state));
75            response.print();
76            return Ok(());
77        }
78
79        if let Some(dl) = deadline
80            && std::time::Instant::now() >= dl
81        {
82            let mut data = build_wait_data(&job_dir, &state);
83            data.exit_code = None;
84            let response = Response::new("wait", data);
85            response.print();
86            return Ok(());
87        }
88
89        std::thread::sleep(poll);
90    }
91}