diff --git a/Cargo.lock b/Cargo.lock index e00df57..356c56d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,7 @@ dependencies = [ "config", "git2", "hex", + "humantime-serde", "lettre", "libc", "regex", @@ -1288,6 +1289,22 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index 17a2197..e1d01b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ fc-evaluator = { path = "./crates/evaluator" } fc-queue-runner = { path = "./crates/queue-runner" } fc-server = { path = "./crates/server" } - anyhow = "1.0.101" argon2 = "0.5.3" askama = "0.15.4" @@ -38,6 +37,7 @@ futures = "0.3.31" git2 = "0.20.4" hex = "0.4.3" hmac = "0.12.1" +humantime-serde = "1.1.1" lettre = { version = "0.11.19", default-features = false, features = [ "tokio1-rustls-tls", "smtp-transport", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 1a55cfc..923db5e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -14,6 +14,7 @@ clap.workspace = true config.workspace = true git2.workspace = true hex.workspace = true +humantime-serde.workspace = true lettre.workspace = true libc.workspace = true regex.workspace = true diff --git a/crates/common/src/config.rs b/crates/common/src/config.rs index c7bedbf..66f65f1 100644 --- a/crates/common/src/config.rs +++ b/crates/common/src/config.rs @@ -1,6 +1,7 @@ //! Configuration management for FC CI use std::path::PathBuf; +use std::time::Duration; use config as config_crate; use serde::{Deserialize, Serialize}; @@ -90,6 +91,12 @@ pub struct QueueRunnerConfig { /// TTL in seconds for failed paths cache entries (default 24h). #[serde(default = "default_failed_paths_ttl")] pub failed_paths_ttl: u64, + + /// Timeout after which builds for unsupported systems are aborted. + /// None or 0 = disabled (Hydra maxUnsupportedTime compatibility). + #[serde(default)] + #[serde(with = "humantime_serde")] + pub unsupported_timeout: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -546,13 +553,14 @@ impl Default for EvaluatorConfig { impl Default for QueueRunnerConfig { fn default() -> Self { Self { - workers: 4, - poll_interval: 5, - build_timeout: 3600, - work_dir: PathBuf::from("/tmp/fc-queue-runner"), - strict_errors: false, - failed_paths_cache: true, - failed_paths_ttl: 86400, + workers: 4, + poll_interval: 5, + build_timeout: 3600, + work_dir: PathBuf::from("/tmp/fc-queue-runner"), + strict_errors: false, + failed_paths_cache: true, + failed_paths_ttl: 86400, + unsupported_timeout: None, } } } @@ -853,4 +861,65 @@ mod tests { env::remove_var("FC_SERVER__PORT"); } } + + #[test] + fn test_unsupported_timeout_config() { + let mut config = Config::default(); + config.queue_runner.unsupported_timeout = Some(Duration::from_secs(3600)); + + // Serialize and deserialize to verify serde works + let toml_str = toml::to_string(&config).unwrap(); + let parsed: Config = toml::from_str(&toml_str).unwrap(); + assert_eq!( + parsed.queue_runner.unsupported_timeout, + Some(Duration::from_secs(3600)) + ); + } + + #[test] + fn test_unsupported_timeout_default() { + let config = Config::default(); + assert_eq!(config.queue_runner.unsupported_timeout, None); + } + + #[test] + fn test_unsupported_timeout_various_formats() { + // Test 30 minutes + let mut config = Config::default(); + config.queue_runner.unsupported_timeout = Some(Duration::from_secs(1800)); + let toml_str = toml::to_string(&config).unwrap(); + let parsed: Config = toml::from_str(&toml_str).unwrap(); + assert_eq!( + parsed.queue_runner.unsupported_timeout, + Some(Duration::from_secs(1800)) + ); + + // Test disabled (0s) + let mut config = Config::default(); + config.queue_runner.unsupported_timeout = Some(Duration::from_secs(0)); + let toml_str = toml::to_string(&config).unwrap(); + let parsed: Config = toml::from_str(&toml_str).unwrap(); + assert_eq!( + parsed.queue_runner.unsupported_timeout, + Some(Duration::from_secs(0)) + ); + } + + #[test] + fn test_humantime_serde_parsing() { + // Test that we can directly parse a QueueRunnerConfig with humantime format + let toml = r#" +workers = 4 +poll_interval = 5 +build_timeout = 3600 +work_dir = "/tmp/fc" +unsupported_timeout = "2h 30m" + "#; + + let qr_config: QueueRunnerConfig = toml::from_str(toml).unwrap(); + assert_eq!( + qr_config.unsupported_timeout, + Some(Duration::from_secs(9000)) // 2.5 hours + ); + } }