From 1b633908fe8babf7a9acd5d703a03f1a20325dd6 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 16 Dec 2025 11:55:58 +0100 Subject: [PATCH 1/8] ci: switch to pr run mode plan only for pr Required to allow different versions inside the monorepo --- .github/workflows/release.yml | 4 ---- Cargo.toml | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7ec7776..f2016eef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -128,10 +128,6 @@ jobs: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y echo "$HOME/.cargo/bin" >> $GITHUB_PATH fi - - uses: swatinem/rust-cache@v2 - with: - key: ${{ join(matrix.targets, '-') }} - cache-provider: ${{ matrix.cache_provider }} - name: Install dist run: ${{ matrix.install_dist.run }} # Get the dist-manifest diff --git a/Cargo.toml b/Cargo.toml index 20457e86..242c5ae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,13 +121,14 @@ installers = ["shell"] # The archive format to use for non-windows builds (defaults .tar.xz) unix-archive = ".tar.gz" # Which actions to run on pull requests -pr-run-mode = "upload" +pr-run-mode = "plan" # Post-announce jobs to run in CI post-announce-jobs = ["./bump-action"] # Path that installers should place binaries in install-path = "CARGO_HOME" # Whether to install an updater program install-updater = false +# Build only the required packages, and individually precise-builds = true [workspace.metadata.dist.github-custom-runners] From 617053c2d0fe2bd279d5669027a9e0cb09fc9678 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 16 Dec 2025 12:03:01 +0100 Subject: [PATCH 2/8] chore: reset exec-harness and memtrack crate versions to 1.0.0 ahead of first release --- Cargo.lock | 4 ++-- crates/exec-harness/Cargo.toml | 2 +- crates/memtrack/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0dada523..53f75f1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,7 +691,7 @@ dependencies = [ [[package]] name = "exec-harness" -version = "4.4.2-beta.1" +version = "1.0.0" dependencies = [ "anyhow", "clap", @@ -1572,7 +1572,7 @@ dependencies = [ [[package]] name = "memtrack" -version = "4.4.2-beta.1" +version = "1.0.0" dependencies = [ "anyhow", "bindgen", diff --git a/crates/exec-harness/Cargo.toml b/crates/exec-harness/Cargo.toml index f2e76116..2eaaf662 100644 --- a/crates/exec-harness/Cargo.toml +++ b/crates/exec-harness/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "exec-harness" -version = "4.4.2-beta.1" +version = "1.0.0" edition = "2024" [dependencies] diff --git a/crates/memtrack/Cargo.toml b/crates/memtrack/Cargo.toml index e1e13625..210e4196 100644 --- a/crates/memtrack/Cargo.toml +++ b/crates/memtrack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "memtrack" -version = "4.4.2-beta.1" +version = "1.0.0" edition = "2024" repository = "https://github.com/CodSpeedHQ/runner" publish = false From 2c3cd61d7d77c6bc1089a3c94fd6bc365e3ad258 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 15 Dec 2025 14:55:47 +0100 Subject: [PATCH 3/8] chore: add cargo-dist arguments for release --- Cargo.toml | 9 ++++++--- crates/exec-harness/Cargo.toml | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 242c5ae7..453809cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,9 +96,12 @@ env_logger = "0.11.8" [workspace.metadata.release] sign-tag = true sign-commit = true -shared-version = true +shared-version = false +consolidate-commits = false +pre-release-commit-message = "chore: Release {{crate_name}} version {{version}} 🎉" + +[package.metadata.release] pre-release-hook = ["./scripts/pre-release.sh", "v{{version}}"] -pre-release-commit-message = "chore: Release runner version {{version}} 🎉" [profile.dist] inherits = "release" @@ -108,7 +111,7 @@ strip = true [package.metadata.dist] targets = ["aarch64-unknown-linux-musl", "x86_64-unknown-linux-musl"] -# Config for 'dist' +# Config for 'dist' for all crates in the workspace [workspace.metadata.dist] # Whether to consider the binaries in a package for distribution (defaults true) dist = true diff --git a/crates/exec-harness/Cargo.toml b/crates/exec-harness/Cargo.toml index 2eaaf662..d36ad4da 100644 --- a/crates/exec-harness/Cargo.toml +++ b/crates/exec-harness/Cargo.toml @@ -2,6 +2,8 @@ name = "exec-harness" version = "1.0.0" edition = "2024" +repository = "https://github.com/CodSpeedHQ/runner" +publish = false [dependencies] anyhow = { workspace = true } @@ -9,3 +11,6 @@ codspeed = "4.1.0" clap = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } + +[package.metadata.dist] +targets = ["aarch64-unknown-linux-musl", "x86_64-unknown-linux-musl"] From 4dda2b7f1513f96a3754fdda197823f54c1657a9 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 15 Dec 2025 18:01:55 +0100 Subject: [PATCH 4/8] feat: auto install codspeed-memtrack during executor setup --- src/binary_installer/mod.rs | 102 +++++++++++++++++++++++ src/binary_installer/versions.rs | 134 +++++++++++++++++++++++++++++++ src/executor/memory/executor.rs | 37 +++++---- src/main.rs | 1 + 4 files changed, 255 insertions(+), 19 deletions(-) create mode 100644 src/binary_installer/mod.rs create mode 100644 src/binary_installer/versions.rs diff --git a/src/binary_installer/mod.rs b/src/binary_installer/mod.rs new file mode 100644 index 00000000..fa5a6b41 --- /dev/null +++ b/src/binary_installer/mod.rs @@ -0,0 +1,102 @@ +use crate::prelude::*; +use crate::run::helpers::download_file; +use semver::Version; +use std::process::Command; +use tempfile::NamedTempFile; +use url::Url; + +mod versions; + +/// Ensure a binary is installed, or install it from a runner's GitHub release using the installer script. +/// +/// This function checks if the binary is already installed with the correct version. +/// If not, it downloads and executes an installer script from the CodSpeed runner repository. +/// +/// # Arguments +/// * `binary_name` - The binary command name (e.g., "codspeed-memtrack", "codspeed-exec-harness") +/// * `version` - The version to install (e.g., "4.4.2-alpha.2") +/// * `get_installer_url` - A closure that returns the URL to download the installer script. +pub async fn ensure_binary_installed( + binary_name: &str, + version: &str, + get_installer_url: F, +) -> Result<()> +where + F: FnOnce() -> String, +{ + if is_command_installed( + binary_name, + Version::parse(version).context("Invalid version format")?, + ) { + debug!("{binary_name} version {version} is already installed"); + return Ok(()); + } + + let installer_url = Url::parse(&get_installer_url()).context("Invalid installer URL")?; + + debug!("Downloading installer from: {installer_url}"); + + // Download the installer script to a temporary file + let temp_file = NamedTempFile::new().context("Failed to create temporary file")?; + download_file(&installer_url, temp_file.path()).await?; + + // Execute the installer script + let output = Command::new("sh") + .arg(temp_file.path()) + .output() + .context("Failed to execute installer command")?; + + if !output.status.success() { + bail!( + "Failed to install {binary_name} version {version}. Installer exited with output: {output:?}", + ); + } + + if !is_command_installed( + binary_name, + Version::parse(version).context("Invalid version format")?, + ) { + bail!( + "Could not veryfy installation of {binary_name} version {version} after running installer" + ); + } + + info!("Successfully installed {binary_name} version {version}"); + Ok(()) +} + +/// Check if the given command is installed and its version matches the expected version. +/// +/// Expects the command to support the `--version` flag and return a version string. +fn is_command_installed(command: &str, expected_version: Version) -> bool { + let is_command_installed = Command::new("which") + .arg(command) + .output() + .is_ok_and(|output| output.status.success()); + + if !is_command_installed { + debug!("{command} is not installed"); + return false; + } + + let Ok(version_output) = Command::new(command).arg("--version").output() else { + return false; + }; + + if !version_output.status.success() { + debug!( + "Failed to get command version. stderr: {}", + String::from_utf8_lossy(&version_output.stderr) + ); + return false; + } + + let version_string = String::from_utf8_lossy(&version_output.stdout); + let Ok(version) = versions::parse_from_output(&version_string) else { + return false; + }; + + debug!("Found {command} version: {version}"); + + versions::is_compatible(command, &version, &expected_version) +} diff --git a/src/binary_installer/versions.rs b/src/binary_installer/versions.rs new file mode 100644 index 00000000..4d12e7de --- /dev/null +++ b/src/binary_installer/versions.rs @@ -0,0 +1,134 @@ +use crate::prelude::*; +use semver::Version; + +/// Parse a version string from command output. +/// +/// Expects the output format to be: "command_name version_string" +/// Example: "codspeed-memtrack 4.4.2" +pub(super) fn parse_from_output(output: &str) -> Result { + let version_str = output + .split_once(" ") + .context("Unexpected version output format: missing space separator")? + .1 + .trim(); + + Version::parse(version_str) + .with_context(|| format!("Failed to parse version from: {version_str}")) +} + +/// Check if an installed version is compatible with the expected version. +/// +/// Returns true if the installed version is greater than or equal to the expected version. +/// Logs warnings for outdated or experimental versions. +pub(super) fn is_compatible(command: &str, installed: &Version, expected: &Version) -> bool { + match installed.cmp(expected) { + std::cmp::Ordering::Less => { + warn!( + "{command} is installed but the version is too old. expecting {expected} or higher but found installed: {installed}", + ); + false + } + std::cmp::Ordering::Greater => { + warn!( + "Using experimental {command} version {installed}. The recommended version is {expected}", + ); + true + } + std::cmp::Ordering::Equal => true, + } +} +#[cfg(test)] +mod tests { + use super::*; + + mod parse_version_from_output { + use super::*; + + #[test] + fn parses_valid_version() { + let output = "codspeed-memtrack 4.4.2"; + let version = parse_from_output(output).unwrap(); + assert_eq!(version, Version::new(4, 4, 2)); + } + + #[test] + fn parses_version_with_prerelease() { + let output = "codspeed-exec-harness 4.4.2-alpha.2"; + let version = parse_from_output(output).unwrap(); + assert_eq!(version.major, 4); + assert_eq!(version.minor, 4); + assert_eq!(version.patch, 2); + assert_eq!(version.pre.as_str(), "alpha.2"); + } + } + + mod is_version_compatible { + use super::*; + + #[test] + fn returns_true_for_equal_versions() { + let installed = Version::new(4, 4, 2); + let expected = Version::new(4, 4, 2); + assert!(is_compatible("test-cmd", &installed, &expected)); + } + + #[test] + fn returns_true_for_newer_version() { + let installed = Version::new(4, 5, 0); + let expected = Version::new(4, 4, 2); + assert!(is_compatible("test-cmd", &installed, &expected)); + } + + #[test] + fn returns_false_for_older_version() { + let installed = Version::new(4, 3, 0); + let expected = Version::new(4, 4, 2); + assert!(!is_compatible("test-cmd", &installed, &expected)); + } + + #[test] + fn handles_prerelease_versions() { + let installed = Version::parse("4.4.2-alpha.2").unwrap(); + let expected = Version::new(4, 4, 1); + // 4.4.2-alpha.2 > 4.4.1 because 4.4.2 > 4.4.1 + assert!(is_compatible("test-cmd", &installed, &expected)); + } + + #[test] + fn prerelease_different_stage() { + { + let installed = Version::parse("4.4.2-alpha.2").unwrap(); + let expected = Version::new(4, 4, 2); + // 4.4.2-alpha.2 < 4.4.2 + assert!(!is_compatible("test-cmd", &installed, &expected)); + } + + { + let installed = Version::parse("4.4.2-beta.1").unwrap(); + let expected = Version::parse("4.4.2-alpha.1").unwrap(); + assert!(is_compatible("test-cmd", &installed, &expected)); + } + + { + let installed = Version::new(4, 4, 2); + let expected = Version::parse("4.4.2-alpha.2").unwrap(); + // 4.4.2 > 4.4.2-alpha.2 + assert!(is_compatible("test-cmd", &installed, &expected)); + } + + { + let installed = Version::parse("4.4.2-alpha.1").unwrap(); + let expected = Version::parse("4.4.2-beta.1").unwrap(); + assert!(!is_compatible("test-cmd", &installed, &expected)); + } + } + + #[test] + fn prerelease_same_stage() { + let installed = Version::parse("4.4.2-alpha.1").unwrap(); + let expected = Version::parse("4.4.2-alpha.2").unwrap(); + + assert!(!is_compatible("test-cmd", &installed, &expected)); + } + } +} diff --git a/src/executor/memory/executor.rs b/src/executor/memory/executor.rs index d75510f2..9a3abb2f 100644 --- a/src/executor/memory/executor.rs +++ b/src/executor/memory/executor.rs @@ -1,3 +1,4 @@ +use crate::binary_installer::ensure_binary_installed; use crate::executor::ExecutorName; use crate::executor::helpers::command::CommandBuilder; use crate::executor::helpers::env::get_base_injected_env; @@ -19,9 +20,12 @@ use runner_shared::artifacts::{ArtifactExt, ExecutionTimestamps}; use runner_shared::fifo::Command as FifoCommand; use runner_shared::fifo::IntegrationMode; use std::path::Path; -use std::process::Command; use std::rc::Rc; use tempfile::NamedTempFile; + +const MEMTRACK_COMMAND: &str = "codspeed-memtrack"; +const MEMTRACK_CODSPEED_VERSION: &str = "1.0.0"; + pub struct MemoryExecutor; impl MemoryExecutor { @@ -30,15 +34,11 @@ impl MemoryExecutor { ) -> Result<(MemtrackIpcServer, CommandBuilder, NamedTempFile)> { // FIXME: We only support native languages for now - // Find memtrack binary - check env variable or use default command name - let memtrack_path = std::env::var("CODSPEED_MEMTRACK_BINARY") - .unwrap_or_else(|_| "codspeed-memtrack".to_string()); - // Setup memtrack IPC server let (ipc_server, server_name) = ipc::IpcOneShotServer::new()?; // Build the memtrack command - let mut cmd_builder = CommandBuilder::new(memtrack_path); + let mut cmd_builder = CommandBuilder::new(MEMTRACK_COMMAND); cmd_builder.arg("track"); cmd_builder.arg("--output"); cmd_builder.arg(execution_context.profile_folder.join("results")); @@ -66,19 +66,18 @@ impl Executor for MemoryExecutor { _system_info: &SystemInfo, _setup_cache_dir: Option<&Path>, ) -> Result<()> { - // Validate that the codspeed-memtrack command is available - let memtrack_path = std::env::var("CODSPEED_MEMTRACK_BINARY") - .unwrap_or_else(|_| "codspeed-memtrack".to_string()); - - info!("Validating memtrack binary at path: {memtrack_path}"); - let output = Command::new(&memtrack_path).arg("--version").output()?; - if !output.status.success() { - bail!( - "codspeed-memtrack command is not available or failed to execute\nstdout: {}\nstderr: {}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } + let get_memtrack_installer_url = || { + format!( + "https://github.com/CodSpeedHQ/runner/releases/download/memtrack-v{MEMTRACK_CODSPEED_VERSION}/memtrack-installer.sh" + ) + }; + + ensure_binary_installed( + MEMTRACK_COMMAND, + MEMTRACK_CODSPEED_VERSION, + get_memtrack_installer_url, + ) + .await?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index a05203d1..f2d6bc4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod api_client; mod app; mod auth; +mod binary_installer; mod config; mod exec; mod executor; From 31584968487086eff124143145d3a9042e55c6a3 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 15 Dec 2025 18:12:43 +0100 Subject: [PATCH 5/8] feat: automatically install exec-harness for exec subcommand --- crates/exec-harness/src/main.rs | 5 ++++- src/exec/mod.rs | 19 +++++++++++++++++++ src/executor/config.rs | 4 ++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/exec-harness/src/main.rs b/crates/exec-harness/src/main.rs index aa48b943..f637729b 100644 --- a/crates/exec-harness/src/main.rs +++ b/crates/exec-harness/src/main.rs @@ -12,7 +12,10 @@ mod walltime; #[derive(Parser, Debug)] #[command(name = "exec-harness")] -#[command(about = "CodSpeed exec harness - wraps commands with performance instrumentation")] +#[command( + version, + about = "CodSpeed exec harness - wraps commands with performance instrumentation" +)] struct Args { /// Optional benchmark name (defaults to command filename) #[arg(long)] diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 79ebbb8a..b1133b63 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -1,4 +1,5 @@ use crate::api_client::CodSpeedAPIClient; +use crate::binary_installer::ensure_binary_installed; use crate::config::CodSpeedConfig; use crate::executor; use crate::prelude::*; @@ -10,6 +11,9 @@ mod poll_results; /// We temporarily force this name for all exec runs pub const DEFAULT_REPOSITORY_NAME: &str = "local-runs"; +pub const EXEC_HARNESS_COMMAND: &str = "exec-harness"; +const EXEC_HARNESS_VERSION: &str = "1.0.0"; + #[derive(Args, Debug)] pub struct ExecArgs { #[command(flatten)] @@ -31,11 +35,26 @@ pub async fn run( ) -> Result<()> { let config = crate::executor::Config::try_from(args)?; let mut execution_context = executor::ExecutionContext::try_from((config, codspeed_config))?; + debug!("config: {:#?}", execution_context.config); let executor = executor::get_executor_from_mode( &execution_context.config.mode, executor::ExecutorCommand::Exec, ); + let get_exec_harness_installer_url = || { + format!( + "https://github.com/CodSpeedHQ/runner/releases/download/exec-harness-v{EXEC_HARNESS_VERSION}/exec-harness-installer.sh" + ) + }; + + // Ensure the exec-harness is installed + ensure_binary_installed( + EXEC_HARNESS_COMMAND, + EXEC_HARNESS_VERSION, + get_exec_harness_installer_url, + ) + .await?; + let poll_results_fn = |run_id: String| poll_results::poll_results(api_client, run_id); executor::execute_benchmarks( diff --git a/src/executor/config.rs b/src/executor/config.rs index 015a839b..a6b61766 100644 --- a/src/executor/config.rs +++ b/src/executor/config.rs @@ -1,4 +1,4 @@ -use crate::exec::DEFAULT_REPOSITORY_NAME; +use crate::exec::{DEFAULT_REPOSITORY_NAME, EXEC_HARNESS_COMMAND}; use crate::instruments::Instruments; use crate::prelude::*; use crate::run::{RunArgs, UnwindingMode}; @@ -141,7 +141,7 @@ impl TryFrom for Config { .map_err(|_| anyhow!("Cannot append to upload URL"))? .push("project"); - let wrapped_command = std::iter::once("exec-harness".to_string()) + let wrapped_command = std::iter::once(EXEC_HARNESS_COMMAND.to_owned()) .chain(args.command) .collect::>() .join(" "); From e3d5c3440aaee9881960f616387e43d2460e533e Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 16 Dec 2025 12:33:17 +0100 Subject: [PATCH 6/8] chore: add CONTRIBUTING.md --- CONTRIBUTING.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a9f3de4f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing to CodSpeed Runner + +## Release Process + +This repository is a Cargo workspace containing multiple crates. The release process differs depending on which crate you're releasing. + +### Workspace Structure + +- **`codspeed-runner`**: The main CLI binary (`codspeed`) +- **`memtrack`**: Memory tracking binary (`codspeed-memtrack`) +- **`exec-harness`**: Execution harness binary +- **`runner-shared`**: Shared library used by other crates + +### Releasing Support Crates (memtrack, exec-harness, runner-shared) + +For any crate other than the main runner: + +```bash +cargo release -p --execute +``` + +Where `` is one of: `alpha`, `beta`, `patch`, `minor`, or `major`. + +**Examples:** + +```bash +# Release a new patch version of memtrack +cargo release -p memtrack --execute patch + +# Release a beta version of exec-harness +cargo release -p exec-harness --execute beta +``` + +#### Post-Release: Update Version References + +After releasing `memtrack` or `exec-harness`, you **must** update the version references in the runner code: + +1. **For memtrack**: Update `MEMTRACK_CODSPEED_VERSION` in `src/executor/memory/executor.rs`: + + ```rust + const MEMTRACK_CODSPEED_VERSION: &str = "X.Y.Z"; // Update to new version + ``` + +2. **For exec-harness**: Update `EXEC_HARNESS_VERSION` in `src/exec/mod.rs`: + ```rust + const EXEC_HARNESS_VERSION: &str = "X.Y.Z"; // Update to new version + ``` + +These constants are used by the runner to download and install the correct versions of the binaries from GitHub releases. + +### Releasing the Main Runner + +The main runner (`codspeed-runner`) should be released after ensuring all dependency versions are correct. + +#### Pre-Release Check + +**Verify binary version references**: Check that version constants in the runner code match the released versions: + +- `MEMTRACK_CODSPEED_VERSION` in `src/executor/memory/executor.rs` +- `EXEC_HARNESS_VERSION` in `src/exec/mod.rs` + +#### Release Command + +```bash +cargo release --execute +``` + +Where `` is one of: `alpha`, `beta`, `patch`, `minor`, or `major`. + +**Examples:** + +```bash +# Release a new minor version +cargo release --execute minor + +# Release a patch version +cargo release --execute patch + +# Release a beta version +cargo release --execute beta +``` + +## Known issue + +If one of the crates is currenlty in beta version, for example the runner is in beta version 4.4.2-beta.1, any alpha release will fail for the any crate, saying that only minor, major or patch releases is supported. From 83e94af08bac2d6f7f534c024cb4c3b0dba8890a Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 16 Dec 2025 16:02:27 +0100 Subject: [PATCH 7/8] fix: remove the password prompt from the run_with_sudo dialog The prompt is the responsibility of the system, let it handle it. --- src/executor/helpers/run_with_sudo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/helpers/run_with_sudo.rs b/src/executor/helpers/run_with_sudo.rs index 25d77416..33d40f14 100644 --- a/src/executor/helpers/run_with_sudo.rs +++ b/src/executor/helpers/run_with_sudo.rs @@ -37,7 +37,7 @@ fn validate_sudo_access() -> Result<()> { if needs_password { suspend_progress_bar(|| { - info!("Sudo privileges are required to continue. Please enter your password."); + info!("Sudo privileges are required to continue."); // Validate and cache sudo credentials let auth_status = Command::new("sudo") From 8be2914bb1ffcfea1bbd341d68271c35ce34948b Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 16 Dec 2025 18:08:18 +0100 Subject: [PATCH 8/8] fix: stop hanging indefinitely if process fails to start in memory executor --- src/executor/memory/executor.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/executor/memory/executor.rs b/src/executor/memory/executor.rs index 9a3abb2f..4a260b5e 100644 --- a/src/executor/memory/executor.rs +++ b/src/executor/memory/executor.rs @@ -22,6 +22,7 @@ use runner_shared::fifo::IntegrationMode; use std::path::Path; use std::rc::Rc; use tempfile::NamedTempFile; +use tokio::time::{Duration, timeout}; const MEMTRACK_COMMAND: &str = "codspeed-memtrack"; const MEMTRACK_CODSPEED_VERSION: &str = "1.0.0"; @@ -130,7 +131,16 @@ impl MemoryExecutor { debug!("handle_fifo called with PID {pid}"); // Accept the IPC connection from memtrack and get the sender it sends us - let (_, memtrack_sender) = ipc.accept()?; + // Use a timeout to prevent hanging if the process doesn't start properly + // https://github.com/servo/ipc-channel/issues/261 + let (_, memtrack_sender) = timeout(Duration::from_secs(5), async move { + tokio::task::spawn_blocking(move || ipc.accept()) + .await + .context("Failed to spawn blocking task")? + .context("Failed to accept IPC connection") + }) + .await + .context("Timeout waiting for IPC connection from memtrack process")??; let ipc_client = Rc::new(MemtrackIpcClient::from_accepted(memtrack_sender)); let ipc_client_health = Rc::clone(&ipc_client);