mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-14 05:43:29 +08:00
fix: detach ecc2 background session runners
This commit is contained in:
@@ -2983,7 +2983,8 @@ async fn spawn_session_runner_for_program(
|
|||||||
working_dir: &Path,
|
working_dir: &Path,
|
||||||
current_exe: &Path,
|
current_exe: &Path,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let child = Command::new(current_exe)
|
let mut command = Command::new(current_exe);
|
||||||
|
command
|
||||||
.arg("run-session")
|
.arg("run-session")
|
||||||
.arg("--session-id")
|
.arg("--session-id")
|
||||||
.arg(session_id)
|
.arg(session_id)
|
||||||
@@ -2995,7 +2996,10 @@ async fn spawn_session_runner_for_program(
|
|||||||
.arg(working_dir)
|
.arg(working_dir)
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null());
|
||||||
|
configure_background_runner_command(&mut command);
|
||||||
|
|
||||||
|
let child = command
|
||||||
.spawn()
|
.spawn()
|
||||||
.with_context(|| format!("Failed to spawn ECC runner from {}", current_exe.display()))?;
|
.with_context(|| format!("Failed to spawn ECC runner from {}", current_exe.display()))?;
|
||||||
|
|
||||||
@@ -3005,6 +3009,35 @@ async fn spawn_session_runner_for_program(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn configure_background_runner_command(command: &mut Command) {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
|
||||||
|
// Detach the runner from the caller's shell/session so it keeps
|
||||||
|
// processing a live harness session after `ecc-tui start` returns.
|
||||||
|
unsafe {
|
||||||
|
command.as_std_mut().pre_exec(|| {
|
||||||
|
if libc::setsid() == -1 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
|
const DETACHED_PROCESS: u32 = 0x0000_0008;
|
||||||
|
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
|
||||||
|
command
|
||||||
|
.as_std_mut()
|
||||||
|
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_agent_command(
|
fn build_agent_command(
|
||||||
cfg: &Config,
|
cfg: &Config,
|
||||||
agent_type: &str,
|
agent_type: &str,
|
||||||
@@ -5032,6 +5065,22 @@ mod tests {
|
|||||||
anyhow::bail!("timed out waiting for {}", path.display());
|
anyhow::bail!("timed out waiting for {}", path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wait_for_text(path: &Path, needle: &str) -> Result<String> {
|
||||||
|
for _ in 0..200 {
|
||||||
|
if path.exists() {
|
||||||
|
let content = fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("failed to read {}", path.display()))?;
|
||||||
|
if content.contains(needle) {
|
||||||
|
return Ok(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(StdDuration::from_millis(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!("timed out waiting for {}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
fn command_env_map(command: &Command) -> BTreeMap<String, String> {
|
fn command_env_map(command: &Command) -> BTreeMap<String, String> {
|
||||||
command
|
command
|
||||||
.as_std()
|
.as_std()
|
||||||
@@ -5047,6 +5096,47 @@ mod tests {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn background_runner_command_starts_new_session() -> Result<()> {
|
||||||
|
let tempdir = TestDir::new("manager-detached-runner")?;
|
||||||
|
let script_path = tempdir.path().join("detached-runner.py");
|
||||||
|
let log_path = tempdir.path().join("detached-runner.log");
|
||||||
|
let script = format!(
|
||||||
|
"#!/usr/bin/env python3\nimport os\nimport pathlib\nimport time\n\npath = pathlib.Path(r\"{}\")\npath.write_text(f\"pid={{os.getpid()}} sid={{os.getsid(0)}}\", encoding=\"utf-8\")\ntime.sleep(30)\n",
|
||||||
|
log_path.display()
|
||||||
|
);
|
||||||
|
fs::write(&script_path, script)?;
|
||||||
|
let mut permissions = fs::metadata(&script_path)?.permissions();
|
||||||
|
permissions.set_mode(0o755);
|
||||||
|
fs::set_permissions(&script_path, permissions)?;
|
||||||
|
|
||||||
|
let mut command = Command::new(&script_path);
|
||||||
|
command
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null());
|
||||||
|
configure_background_runner_command(&mut command);
|
||||||
|
|
||||||
|
let mut child = command.spawn()?;
|
||||||
|
let child_pid = child.id().context("detached child pid")? as i32;
|
||||||
|
let content = wait_for_text(&log_path, "sid=")?;
|
||||||
|
let sid = content
|
||||||
|
.split_whitespace()
|
||||||
|
.find_map(|part| part.strip_prefix("sid="))
|
||||||
|
.context("session id should be logged")?
|
||||||
|
.parse::<i32>()
|
||||||
|
.context("session id should parse")?;
|
||||||
|
let parent_sid = unsafe { libc::getsid(0) };
|
||||||
|
|
||||||
|
assert_eq!(sid, child_pid);
|
||||||
|
assert_ne!(sid, parent_sid);
|
||||||
|
|
||||||
|
let _ = child.kill().await;
|
||||||
|
let _ = child.wait().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_package_manager_project_files(
|
fn write_package_manager_project_files(
|
||||||
repo_root: &Path,
|
repo_root: &Path,
|
||||||
package_manager_field: Option<&str>,
|
package_manager_field: Option<&str>,
|
||||||
|
|||||||
Reference in New Issue
Block a user