From 999eb165ec2a9fa3ba51ef5ca61c67f424bd4272 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Sun, 10 Aug 2025 00:47:52 +0400 Subject: [PATCH] refactor: add execution retries on windows --- src/template/src/main.rs | 56 ++++++++++++++++++++++++++++++---------- tests/common/mod.rs | 30 ++++++++++++++++++--- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/template/src/main.rs b/src/template/src/main.rs index b5be622..a6194cf 100644 --- a/src/template/src/main.rs +++ b/src/template/src/main.rs @@ -292,20 +292,48 @@ fn run_app(app_dir: &Path, args: &[String]) -> Result<()> { let mut cmd_args = vec![main_script.clone()]; cmd_args.extend(args.iter().cloned()); - // Execute Node.js application - let status = Command::new(&node_executable) - .args(&cmd_args) - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status() - .with_context(|| format!( - "Failed to execute Node.js application\nExecutable: {}\nMain script: {}\nArgs: {:?}\nWorking directory: {}", - node_executable.display(), - main_script, - cmd_args, - app_path.display() - ))?; + // Execute Node.js application with a few retries to tolerate transient Windows issues + let mut status = None; + let mut last_err: Option = None; + let max_attempts: u32 = 8; + for attempt in 1..=max_attempts { + match Command::new(&node_executable) + .args(&cmd_args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + { + Ok(s) => { + status = Some(s); + break; + } + Err(e) => { + last_err = Some(anyhow::anyhow!(e).context(format!( + "Failed to execute Node.js application (attempt {attempt}/{max_attempts})\nExecutable: {}\nMain script: {}\nArgs: {:?}\nWorking directory: {}", + node_executable.display(), + main_script, + cmd_args, + app_path.display() + ))); + #[cfg(windows)] + { + use std::time::Duration; + std::thread::sleep(Duration::from_millis(50 * attempt as u64)); + } + #[cfg(not(windows))] + { + if attempt >= 2 { + break; + } + } + } + } + } + let status = status.ok_or_else(|| last_err.unwrap_or_else(|| anyhow::anyhow!( + "Failed to execute Node.js application after {} attempts", + max_attempts + )))?; std::process::exit(status.code().unwrap_or(1)); } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 5a55ee7..70b7721 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -675,7 +675,31 @@ impl BundlerTestHelper { } // Build command to run the executable. - let mut cmd = Command::new(executable_path); + // On Windows CI, concurrent CreateProcess on the same path may fail sporadically. + // Copy the binary to a unique temp dir to shorten the path and avoid races. + #[cfg(windows)] + let (exec_path_owned, _temp_guard) = { + let tmp = TempDir::new().context("Failed to create temp dir for executable copy")?; + let file_name = executable_path + .file_name() + .ok_or_else(|| anyhow::anyhow!( + "Executable path missing file name: {}", + executable_path.display() + ))?; + let dest = tmp.path().join(file_name); + std::fs::copy(executable_path, &dest).with_context(|| { + format!( + "Failed to copy executable to temporary directory: {} -> {}", + executable_path.display(), + dest.display() + ) + })?; + (dest, tmp) + }; + #[cfg(not(windows))] + let exec_path_owned = executable_path.to_path_buf(); + + let mut cmd = Command::new(&exec_path_owned); cmd.args(args); @@ -685,14 +709,14 @@ impl BundlerTestHelper { println!( "Executing: {} with args: {:?}", - executable_path.display(), + exec_path_owned.display(), args ); let output = cmd.output().with_context(|| { format!( "Failed to execute command: {}\nArgs: {:?}\nEnv vars: {:?}\nWorking directory: {:?}", - executable_path.display(), + exec_path_owned.display(), args, env_vars, std::env::current_dir().unwrap_or_else(|_| "".into())