use crate::attach;
use crate::control::{self, Handle, LoopMessage};
use crate::pane::{ExitInfo, OutputHub, Pane};
use crate::paths;
use crate::session::{self, Meta, State, Status};
use anyhow::{Context, Result};
use chrono::Utc;
use std::io::{IsTerminal, Write};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::{mpsc, watch};
pub async fn run(
cmd: Vec<String>,
id: Option<String>,
detach: bool,
detached_id: Option<String>,
) -> Result<i32> {
if let Some(worker_id) = detached_id {
serve_worker(cmd, worker_id).await?;
return Ok(0);
}
let session_id = session::make_id(id).await?;
print_banner(&session_id, &cmd.join(" "));
spawn_worker_process(&cmd, &session_id)?;
if detach {
return Ok(0);
}
attach::attach_to(session_id).await
}
async fn serve_worker(cmd: Vec<String>, id: String) -> Result<()> {
let meta = Meta {
id: id.clone(),
cmd: cmd.clone(),
babysit_pid: std::process::id(),
started_at: Utc::now(),
};
session::write_meta(&meta).await?;
session::write_status(&id, &Status::starting()).await?;
let (cols, rows) = (80u16, 24u16);
let log_path = paths::output_log_path(&id)?;
let env = vec![("BABYSIT_SESSION_ID".into(), id.clone())];
let hub = OutputHub::new();
let pane = match Pane::spawn(&cmd, rows, cols, &env, Some(&log_path), hub.clone()) {
Ok(p) => Arc::new(p),
Err(e) => {
let _ = session::write_status(
&id,
&Status {
state: State::Exited,
child_pid: None,
exit_code: None,
last_change: Utc::now(),
},
)
.await;
return Err(e);
}
};
session::write_status(
&id,
&Status {
state: State::Running,
child_pid: pane.pid,
exit_code: None,
last_change: Utc::now(),
},
)
.await?;
let (action_tx, mut action_rx) = mpsc::unbounded_channel::<LoopMessage>();
let (exit_tx, exit_rx) = watch::channel::<Option<ExitInfo>>(None);
let (detach_tx, _detach_rx0) = watch::channel::<u64>(0);
let detach_tx = Arc::new(detach_tx);
let attached = Arc::new(AtomicUsize::new(0));
let handle = Handle::new(
id.clone(),
pane.clone(),
action_tx,
hub.clone(),
exit_rx,
detach_tx,
attached.clone(),
);
control::serve(handle.clone()).await?;
let mut current_pane = pane;
let info: Option<ExitInfo>;
loop {
let exit_notify = current_pane.exit_notify.clone();
tokio::select! {
Some(msg) = action_rx.recv() => match msg {
LoopMessage::Restart => {
current_pane.kill();
current_pane.exit_notify.notified().await;
let new_pane = Arc::new(Pane::spawn(&cmd, rows, cols, &env, Some(&log_path), hub.clone())?);
handle.replace_cmd_pane(new_pane.clone()).await;
session::write_status(&id, &Status {
state: State::Running,
child_pid: new_pane.pid,
exit_code: None,
last_change: Utc::now(),
}).await?;
current_pane = new_pane;
}
},
_ = exit_notify.notified() => {
info = current_pane.exit_info();
let signaled = info.map(|i| i.signaled).unwrap_or(true);
let state = if signaled { State::Killed } else { State::Exited };
session::write_status(&id, &Status {
state,
child_pid: None,
exit_code: info.and_then(|i| i.code),
last_change: Utc::now(),
}).await?;
break;
}
}
}
let _ = tokio::time::timeout(
std::time::Duration::from_millis(500),
current_pane.reader_done.notified(),
)
.await;
let _ = exit_tx.send(Some(info.unwrap_or(ExitInfo {
code: None,
signaled: true,
})));
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
while attached.load(Ordering::SeqCst) > 0 && std::time::Instant::now() < deadline {
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
control::cleanup(&id);
Ok(())
}
fn print_banner(id: &str, cmd_title: &str) {
let (on, off) = if std::io::stdout().is_terminal() {
("\x1b[1;36m", "\x1b[0m")
} else {
("", "")
};
println!("babysit session {on}{id}{off}: {cmd_title}");
println!(" babysit log -s {on}{id}{off} --tail 200");
println!(" babysit attach -s {on}{id}{off}");
let _ = std::io::stdout().flush();
}
fn spawn_worker_process(cmd: &[String], id: &str) -> Result<()> {
use std::process::{Command, Stdio};
let exe = std::env::current_exe().context("locating the babysit executable")?;
let mut command = Command::new(exe);
command.arg("run").arg("--detached-id").arg(id);
command.arg("--").args(cmd);
command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
unsafe {
command.pre_exec(|| {
nix::unistd::setsid().map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
Ok(())
});
}
}
command
.spawn()
.context("spawning detached babysit worker")?;
Ok(())
}