diff --git a/src/commands.rs b/src/commands.rs index a07f77674..8f271d986 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -32,6 +32,7 @@ use std::io::{self, Write}; use std::os::unix::process::ExitStatusExt; use std::path::Path; use std::process; +use std::time::Duration; use strip_ansi_escapes::Writer; use tokio::io::AsyncReadExt; use tokio::runtime::Runtime; @@ -43,7 +44,7 @@ use crate::errors::*; pub const DEFAULT_PORT: u16 = 4226; /// The number of milliseconds to wait for server startup. -const SERVER_STARTUP_TIMEOUT_MS: u32 = 10000; +const SERVER_STARTUP_TIMEOUT: Duration = Duration::from_millis(10000); /// Get the port on which the server should listen. fn get_port() -> u16 { @@ -78,9 +79,7 @@ async fn read_server_startup_status( /// Re-execute the current executable as a background server, and wait /// for it to start up. #[cfg(not(windows))] -fn run_server_process() -> Result { - use std::time::Duration; - +fn run_server_process(startup_timeout: Option) -> Result { trace!("run_server_process"); let tempdir = tempfile::Builder::new().prefix("sccache").tempdir()?; let socket_path = tempdir.path().join("sock"); @@ -101,7 +100,7 @@ fn run_server_process() -> Result { read_server_startup_status(socket).await }; - let timeout = Duration::from_millis(SERVER_STARTUP_TIMEOUT_MS.into()); + let timeout = startup_timeout.unwrap_or(SERVER_STARTUP_TIMEOUT); runtime.block_on(async move { match tokio::time::timeout(timeout, startup).await { Ok(result) => result, @@ -159,12 +158,11 @@ fn redirect_error_log(f: File) -> Result<()> { /// Re-execute the current executable as a background server. #[cfg(windows)] -fn run_server_process() -> Result { +fn run_server_process(startup_timeout: Option) -> Result { use futures::StreamExt; use std::mem; use std::os::windows::ffi::OsStrExt; use std::ptr; - use std::time::Duration; use uuid::Uuid; use winapi::shared::minwindef::{DWORD, FALSE, LPVOID, TRUE}; use winapi::um::handleapi::CloseHandle; @@ -256,7 +254,7 @@ fn run_server_process() -> Result { read_server_startup_status(socket?).await }; - let timeout = Duration::from_millis(SERVER_STARTUP_TIMEOUT_MS.into()); + let timeout = startup_timeout.unwrap_or(SERVER_STARTUP_TIMEOUT); runtime.block_on(async move { match tokio::time::timeout(timeout, startup).await { Ok(result) => result, @@ -266,7 +264,10 @@ fn run_server_process() -> Result { } /// Attempt to connect to an sccache server listening on `port`, or start one if no server is running. -fn connect_or_start_server(port: u16) -> Result { +fn connect_or_start_server( + port: u16, + startup_timeout: Option, +) -> Result { trace!("connect_or_start_server({})", port); match connect_to_server(port) { Ok(server) => Ok(server), @@ -276,7 +277,7 @@ fn connect_or_start_server(port: u16) -> Result { { // If the connection was refused we probably need to start // the server. - match run_server_process()? { + match run_server_process(startup_timeout)? { ServerStartup::Ok { port: actualport } => { if port != actualport { // bail as the next connect_with_retry will fail @@ -578,11 +579,12 @@ pub fn run_command(cmd: Command) -> Result { // Config isn't required for all commands, but if it's broken then we should flag // it early and loudly. let config = &Config::load()?; + let startup_timeout = config.server_startup_timeout; match cmd { Command::ShowStats(fmt) => { trace!("Command::ShowStats({:?})", fmt); - let srv = connect_or_start_server(get_port())?; + let srv = connect_or_start_server(get_port(), startup_timeout)?; let stats = request_stats(srv).context("failed to get stats from server")?; match fmt { StatsFormat::Text => stats.print(), @@ -605,7 +607,8 @@ pub fn run_command(cmd: Command) -> Result { Command::StartServer => { trace!("Command::StartServer"); println!("sccache: Starting the server..."); - let startup = run_server_process().context("failed to start server process")?; + let startup = + run_server_process(startup_timeout).context("failed to start server process")?; match startup { ServerStartup::Ok { port } => { if port != DEFAULT_PORT { @@ -626,7 +629,7 @@ pub fn run_command(cmd: Command) -> Result { } Command::ZeroStats => { trace!("Command::ZeroStats"); - let conn = connect_or_start_server(get_port())?; + let conn = connect_or_start_server(get_port(), startup_timeout)?; let stats = request_zero_stats(conn).context("couldn't zero stats on server")?; stats.print(); } @@ -688,7 +691,7 @@ pub fn run_command(cmd: Command) -> Result { ), Command::DistStatus => { trace!("Command::DistStatus"); - let srv = connect_or_start_server(get_port())?; + let srv = connect_or_start_server(get_port(), startup_timeout)?; let status = request_dist_status(srv).context("failed to get dist-status from server")?; serde_json::to_writer(&mut io::stdout(), &status)?; @@ -726,7 +729,7 @@ pub fn run_command(cmd: Command) -> Result { } => { trace!("Command::Compile {{ {:?}, {:?}, {:?} }}", exe, cmdline, cwd); let jobserver = unsafe { Client::new() }; - let conn = connect_or_start_server(get_port())?; + let conn = connect_or_start_server(get_port(), startup_timeout)?; let mut runtime = Runtime::new()?; let res = do_compile( ProcessCommandCreator::new(&jobserver), diff --git a/src/config.rs b/src/config.rs index 43310f311..2a247db81 100644 --- a/src/config.rs +++ b/src/config.rs @@ -431,6 +431,7 @@ impl Default for DistConfig { pub struct FileConfig { pub cache: CacheConfigs, pub dist: DistConfig, + pub server_startup_timeout_ms: Option, } // If the file doesn't exist or we can't read it, log the issue and proceed. If the @@ -657,10 +658,11 @@ pub struct Config { pub caches: Vec, pub fallback_cache: DiskCacheConfig, pub dist: DistConfig, + pub server_startup_timeout: Option, } impl Config { - pub fn load() -> Result { + pub fn load() -> Result { let env_conf = config_from_env()?; let file_conf_path = config_file("SCCACHE_CONF", "config"); @@ -668,23 +670,31 @@ impl Config { .context("Failed to load config file")? .unwrap_or_default(); - Ok(Config::from_env_and_file_configs(env_conf, file_conf)) + Ok(Self::from_env_and_file_configs(env_conf, file_conf)) } - fn from_env_and_file_configs(env_conf: EnvConfig, file_conf: FileConfig) -> Config { + fn from_env_and_file_configs(env_conf: EnvConfig, file_conf: FileConfig) -> Self { let mut conf_caches: CacheConfigs = Default::default(); - let FileConfig { cache, dist } = file_conf; + let FileConfig { + cache, + dist, + server_startup_timeout_ms, + } = file_conf; conf_caches.merge(cache); + let server_startup_timeout = + server_startup_timeout_ms.map(std::time::Duration::from_millis); + let EnvConfig { cache } = env_conf; conf_caches.merge(cache); let (caches, fallback_cache) = conf_caches.into_vec_and_fallback(); - Config { + Self { caches, fallback_cache, dist, + server_startup_timeout, } } } @@ -926,6 +936,7 @@ fn config_overrides() { ..Default::default() }, dist: Default::default(), + server_startup_timeout_ms: None, }; assert_eq!( @@ -947,6 +958,7 @@ fn config_overrides() { size: 5, }, dist: Default::default(), + server_startup_timeout: None, } ); } @@ -1024,6 +1036,8 @@ fn test_gcs_oauth_url() { #[test] fn full_toml_parse() { const CONFIG_STR: &str = r#" +server_startup_timeout_ms = 10000 + [dist] # where to find the scheduler scheduler_url = "http://1.2.3.4:10600" @@ -1131,6 +1145,7 @@ no_credentials = true toolchain_cache_size: 5368709120, rewrite_includes_only: false, }, + server_startup_timeout_ms: Some(10000), } ) } diff --git a/tests/harness/mod.rs b/tests/harness/mod.rs index 9df61a6dd..d8b5e11c0 100644 --- a/tests/harness/mod.rs +++ b/tests/harness/mod.rs @@ -154,6 +154,7 @@ pub fn sccache_client_cfg(tmpdir: &Path) -> sccache::config::FileConfig { toolchain_cache_size: TC_CACHE_SIZE, rewrite_includes_only: false, // TODO }, + server_startup_timeout_ms: None, } } #[cfg(feature = "dist-server")] diff --git a/tests/oauth.rs b/tests/oauth.rs index 59ae9549c..2093d34ce 100755 --- a/tests/oauth.rs +++ b/tests/oauth.rs @@ -59,6 +59,7 @@ fn config_with_dist_auth( toolchain_cache_size: 0, rewrite_includes_only: true, }, + server_startup_timeout_ms: None, } }