Skip to content

Commit 399f920

Browse files
authored
fix(windows): handle runtime process lifecycle
- Improve Windows-aware runtime process lifecycle handling - Adjust parent-exit and command server process behavior - Keep production runtime fixes separate from test fixture updates
1 parent 92f5432 commit 399f920

6 files changed

Lines changed: 33 additions & 21 deletions

File tree

crates/aionui-ai-agent/src/capability/cli_process/stderr_monitor.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ pub(super) fn force_kill(pid: u32, process_group_id: Option<u32>) -> Result<(),
6161
}
6262
#[cfg(windows)]
6363
{
64+
let _ = process_group_id;
65+
6466
// `taskkill` exit codes:
6567
// 0 — process killed
6668
// 128 — "not found" (already exited): treat as success, identical to
@@ -75,7 +77,7 @@ pub(super) fn force_kill(pid: u32, process_group_id: Option<u32>) -> Result<(),
7577
debug!(pid, "taskkill /F /T succeeded");
7678
Ok(())
7779
}
78-
Ok(output) if output.status.code() == Some(128) => {
80+
Ok(output) if taskkill_output_is_missing_process(&output) => {
7981
debug!(pid, "Process already exited before taskkill");
8082
Ok(())
8183
}
@@ -101,6 +103,16 @@ pub(super) fn force_kill(pid: u32, process_group_id: Option<u32>) -> Result<(),
101103
}
102104
}
103105

106+
#[cfg(windows)]
107+
fn taskkill_output_is_missing_process(output: &std::process::Output) -> bool {
108+
if output.status.code() == Some(128) {
109+
return true;
110+
}
111+
112+
let stderr = String::from_utf8_lossy(&output.stderr).to_ascii_lowercase();
113+
stderr.contains("no running instance") || stderr.contains("not found")
114+
}
115+
104116
#[cfg(test)]
105117
mod force_kill_tests {
106118
use super::force_kill;

crates/aionui-app/src/bootstrap/parent_exit.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async fn wait_for_parent_exit(parent_pid: u32) {
3030

3131
#[cfg(windows)]
3232
fn wait_for_parent_exit_blocking(parent_pid: u32) {
33-
use windows_sys::Win32::Foundation::{CloseHandle, WAIT_OBJECT_0};
33+
use windows_sys::Win32::Foundation::CloseHandle;
3434
use windows_sys::Win32::System::Threading::{
3535
INFINITE, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, WaitForSingleObject,
3636
};
@@ -41,11 +41,10 @@ fn wait_for_parent_exit_blocking(parent_pid: u32) {
4141
return;
4242
}
4343

44-
let wait_result = WaitForSingleObject(handle, INFINITE);
44+
// Any wait completion ends the parent-exit monitor; there is no
45+
// distinct error path because callers only need a shutdown signal.
46+
let _ = WaitForSingleObject(handle, INFINITE);
4547
let _ = CloseHandle(handle);
46-
if wait_result != WAIT_OBJECT_0 {
47-
return;
48-
}
4948
}
5049
}
5150

crates/aionui-app/src/commands/cmd_server.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const WORKER_TASK_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
2222
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2323
enum ShutdownReason {
2424
Sigint,
25+
#[cfg(unix)]
2526
Sigterm,
2627
ParentExit,
2728
}
@@ -286,6 +287,7 @@ pub(crate) async fn run_server(
286287
Ok(reason) => {
287288
match reason {
288289
ShutdownReason::Sigint => info!("Received SIGINT, shutting down..."),
290+
#[cfg(unix)]
289291
ShutdownReason::Sigterm => info!("Received SIGTERM, shutting down..."),
290292
ShutdownReason::ParentExit => info!("Detected desktop parent exit, shutting down..."),
291293
}

crates/aionui-extension/src/skill_service.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,12 +2135,7 @@ async fn create_symlink(src: &Path, dst: &Path) -> Result<(), ExtensionError> {
21352135
let dst = dst.to_path_buf();
21362136
tokio::task::spawn_blocking(move || junction::create(&src, &dst))
21372137
.await
2138-
.map_err(|e| {
2139-
ExtensionError::Io(std::io::Error::new(
2140-
std::io::ErrorKind::Other,
2141-
format!("junction::create join error: {e}"),
2142-
))
2143-
})?
2138+
.map_err(|e| ExtensionError::Io(std::io::Error::other(format!("junction::create join error: {e}"))))?
21442139
.map_err(ExtensionError::Io)
21452140
} else {
21462141
tokio::fs::symlink_file(src, dst).await.map_err(ExtensionError::Io)

crates/aionui-runtime/src/agent_env.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use std::collections::BTreeMap;
22
use std::ffi::OsString;
3-
use std::path::{Path, PathBuf};
3+
#[cfg(unix)]
4+
use std::path::Path;
5+
use std::path::PathBuf;
6+
#[cfg(unix)]
47
use std::time::Duration;
58

69
use tokio::sync::OnceCell;
710

11+
#[cfg(unix)]
812
use crate::Builder;
913

1014
static FULL_SHELL_ENV: OnceCell<Vec<(OsString, OsString)>> = OnceCell::const_new();
@@ -146,6 +150,7 @@ async fn load_full_shell_env() -> Vec<(OsString, OsString)> {
146150
Vec::new()
147151
}
148152

153+
#[cfg(unix)]
149154
fn parse_env_output(output: &str) -> Vec<(OsString, OsString)> {
150155
let mut parsed = Vec::new();
151156
let mut current_key: Option<String> = None;
@@ -170,6 +175,7 @@ fn parse_env_output(output: &str) -> Vec<(OsString, OsString)> {
170175
parsed
171176
}
172177

178+
#[cfg(unix)]
173179
fn parse_env_start(line: &str) -> Option<(&str, &str)> {
174180
let (key, value) = line.split_once('=')?;
175181
if is_valid_env_key(key) {
@@ -179,6 +185,7 @@ fn parse_env_start(line: &str) -> Option<(&str, &str)> {
179185
}
180186
}
181187

188+
#[cfg(unix)]
182189
fn is_valid_env_key(key: &str) -> bool {
183190
let mut chars = key.chars();
184191
let Some(first) = chars.next() else {

crates/aionui-runtime/src/spawn.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -340,14 +340,11 @@ async fn kill_windows_process_tree(pid: u32) -> io::Result<()> {
340340
return Ok(());
341341
}
342342

343-
Err(io::Error::new(
344-
io::ErrorKind::Other,
345-
format!(
346-
"taskkill failed for pid {pid} (exit {:?}): {}",
347-
output.status.code(),
348-
String::from_utf8_lossy(&output.stderr)
349-
),
350-
))
343+
Err(io::Error::other(format!(
344+
"taskkill failed for pid {pid} (exit {:?}): {}",
345+
output.status.code(),
346+
String::from_utf8_lossy(&output.stderr)
347+
)))
351348
}
352349

353350
/// Resolve `program` through `resolve_command_path` so callers don't have

0 commit comments

Comments
 (0)