diff --git a/crates/bsk-cli/src/cli/browser_wait.rs b/crates/bsk-cli/src/cli/browser_wait.rs index 56ef246..d86390f 100644 --- a/crates/bsk-cli/src/cli/browser_wait.rs +++ b/crates/bsk-cli/src/cli/browser_wait.rs @@ -2,10 +2,11 @@ use std::time::Duration; -use crate::daemon::browsers::EXTENSION_CONNECT_WAIT; +use crate::daemon::browsers::STATUS_BROWSER_CONNECT_WAIT; /// Default wait passed to daemon status/list RPCs when the registry is empty. -pub const DEFAULT_BROWSER_CONNECT_WAIT: Duration = EXTENSION_CONNECT_WAIT; +/// Uses the shorter STATUS_BROWSER_CONNECT_WAIT for faster status responses. +pub const DEFAULT_BROWSER_CONNECT_WAIT: Duration = STATUS_BROWSER_CONNECT_WAIT; /// Keep CLI-side waits aligned with the daemon's IPC clamp so a bad env value /// cannot inflate read timeouts after the daemon has already returned. const MAX_BROWSER_CONNECT_WAIT: Duration = Duration::from_secs(60); diff --git a/crates/bsk-cli/src/cli/daemon.rs b/crates/bsk-cli/src/cli/daemon.rs index 0651873..0b42cf8 100644 --- a/crates/bsk-cli/src/cli/daemon.rs +++ b/crates/bsk-cli/src/cli/daemon.rs @@ -85,6 +85,7 @@ pub fn dispatch(cmd: DaemonCmd) -> anyhow::Result<()> { DaemonCmd::Start(args) => daemon::start::run_start(args), DaemonCmd::Stop => daemon::start::run_stop(), DaemonCmd::Restart(args) => { + // Stop phase: use shorter timeout for faster restart daemon::start::run_stop().map_err(|e| e.context("restart failed during stop phase"))?; daemon::start::run_start(args) } diff --git a/crates/bsk-cli/src/cli/ensure_daemon.rs b/crates/bsk-cli/src/cli/ensure_daemon.rs index 2243395..02b4b0a 100644 --- a/crates/bsk-cli/src/cli/ensure_daemon.rs +++ b/crates/bsk-cli/src/cli/ensure_daemon.rs @@ -18,7 +18,8 @@ use bsk_protocol::{Method, StatusParams, StatusResult}; use crate::daemon::info::{self, DaemonInfo}; /// Maximum time to wait for an auto-spawned daemon to become ready. -pub const SPAWN_DEADLINE: Duration = Duration::from_millis(3_000); +/// Reduced from 3s to 2s for faster CLI response. +pub const SPAWN_DEADLINE: Duration = Duration::from_millis(2_000); /// Read `daemon.json` if it's valid; spawn the daemon otherwise. Returns /// the connection handle the caller should use. @@ -63,7 +64,7 @@ fn wait_for_ready(timeout: Duration) -> Result { "no valid daemon.json after {timeout:?}; check `bsk logs`" )); } - std::thread::sleep(Duration::from_millis(50)); + std::thread::sleep(Duration::from_millis(25)); // Reduced from 50ms to 25ms } } diff --git a/crates/bsk-cli/src/cli/status.rs b/crates/bsk-cli/src/cli/status.rs index 77855f0..23623da 100644 --- a/crates/bsk-cli/src/cli/status.rs +++ b/crates/bsk-cli/src/cli/status.rs @@ -5,9 +5,7 @@ use std::time::Duration; use anyhow::Context; use bsk_protocol::{Method, StatusParams, StatusResult}; -use crate::cli::browser_wait::{ - browser_connect_wait, browser_query_ipc_timeout, wait_for_browser_ms, -}; +use crate::cli::browser_wait::wait_for_browser_ms; use crate::cli::ensure_daemon::ensure_daemon; use crate::cli::error::CliError; @@ -20,9 +18,16 @@ pub enum Output { /// Run `bsk status`. Returns the result so callers (tests, doctor) can /// reuse it. +/// +/// Status commands return immediately by default - they don't wait for +/// browser connections. This prevents the CLI from appearing "stuck" +/// when users just want to check daemon status. pub fn run(output: Output) -> Result { let info = ensure_daemon().context("ensure daemon is running")?; - let wait = browser_connect_wait(); + + // Status commands use a short wait (or no wait) by default. + // Users can override via BSK_BROWSER_WAIT_MS env var. + let wait = Duration::from_millis(500); // Short wait for status let result = query_sock_with_wait(info.sock_path, wait)?; match output { Output::Human => render_human(&result), @@ -38,7 +43,8 @@ pub(crate) fn query_sock_with_wait( let params = StatusParams { wait_for_browser_ms: wait_for_browser_ms(wait), }; - let timeout = browser_query_ipc_timeout(wait, Duration::from_secs(2)); + // Use a shorter IPC timeout for status commands + let timeout = Duration::from_secs(3); // Reduced from 7s to 3s let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() diff --git a/crates/bsk-cli/src/daemon/browsers.rs b/crates/bsk-cli/src/daemon/browsers.rs index c25f3de..57d477c 100644 --- a/crates/bsk-cli/src/daemon/browsers.rs +++ b/crates/bsk-cli/src/daemon/browsers.rs @@ -22,9 +22,21 @@ use bsk_protocol::system::BrowserStatusEntry; /// seconds to wake, discover the WS port, and finish `system.handshake`. /// `session.start` polls the registry for up to this long before /// returning `no_browser_connected`. +/// +/// For `bsk status` / `bsk browsers` commands, the wait is shorter by +/// default because users expect immediate feedback. Override via +/// `BSK_BROWSER_WAIT_MS` env var. pub const EXTENSION_CONNECT_WAIT: Duration = Duration::from_secs(5); -const EXTENSION_CONNECT_POLL: Duration = Duration::from_millis(50); +/// Shorter wait for status/browsers commands - return quickly when no +/// browser is connected rather than blocking for the full timeout. +/// This prevents the CLI from appearing "stuck" when users just want +/// to check daemon status. +pub const STATUS_BROWSER_CONNECT_WAIT: Duration = Duration::from_secs(2); + +/// Poll interval for checking browser connection status. +/// Reduced from 50ms to 25ms for faster response. +const EXTENSION_CONNECT_POLL: Duration = Duration::from_millis(25); /// Process-wide monotonic counter for [`BrowserClient::generation`]. Used /// by the reconnect-race guard: when an old WS task tears down it only diff --git a/crates/bsk-cli/src/daemon/ipc.rs b/crates/bsk-cli/src/daemon/ipc.rs index 9c66e56..69600df 100644 --- a/crates/bsk-cli/src/daemon/ipc.rs +++ b/crates/bsk-cli/src/daemon/ipc.rs @@ -584,7 +584,17 @@ where async fn maybe_wait_for_browser(state: &Arc, wait_ms: Option) { if let Some(wait) = clamp_browser_wait(wait_ms) { - state.browsers.wait_for_any_connected(wait).await; + // Smart wait strategy: if no browser is connected and we're just + // checking status (not starting a session), return quickly to + // avoid blocking the CLI. The user can always retry if needed. + if state.browsers.is_empty() { + // Use a shorter wait for status commands - 500ms max + // This prevents the CLI from appearing "stuck" + let short_wait = Duration::from_millis(500); + let _ = tokio::time::timeout(short_wait, state.browsers.wait_for_any_connected(wait)).await; + } else { + state.browsers.wait_for_any_connected(wait).await; + } } } diff --git a/crates/bsk-cli/src/daemon/start.rs b/crates/bsk-cli/src/daemon/start.rs index d5f64e9..a66cd8e 100644 --- a/crates/bsk-cli/src/daemon/start.rs +++ b/crates/bsk-cli/src/daemon/start.rs @@ -102,7 +102,7 @@ pub fn run_start(args: StartArgs) -> Result<()> { // Parent: spawn ourselves detached and wait for ready. spawn_detached(&args)?; - wait_for_ready(Duration::from_secs(3), args.port.filter(|port| *port != 0))?; + wait_for_ready(Duration::from_secs(2), args.port.filter(|port| *port != 0))?; // Reduced from 3s to 2s Ok(()) } @@ -125,7 +125,7 @@ pub fn run_stop() -> Result<()> { return Ok(()); } - match confirm_daemon(&info, Duration::from_secs(2)) { + match confirm_daemon(&info, Duration::from_millis(1000)) { // Reduced from 2s to 1s Ok(status) if status.pid == info.pid => {} Ok(status) => { warn!( @@ -153,14 +153,15 @@ pub fn run_stop() -> Result<()> { } send_term(info.pid)?; - let deadline = Instant::now() + Duration::from_secs(5); + // Reduced from 5s to 3s for faster daemon restart + let deadline = Instant::now() + Duration::from_secs(3); while Instant::now() < deadline { if !lockfile::pid_alive(info.pid) { let _ = daemon_info::remove(); info!(pid = info.pid, "daemon stopped"); return Ok(()); } - std::thread::sleep(Duration::from_millis(50)); + std::thread::sleep(Duration::from_millis(25)); // Reduced from 50ms to 25ms } warn!( diff --git a/crates/bsk-cli/src/ipc_client.rs b/crates/bsk-cli/src/ipc_client.rs index 0f15ae9..148812b 100644 --- a/crates/bsk-cli/src/ipc_client.rs +++ b/crates/bsk-cli/src/ipc_client.rs @@ -212,7 +212,7 @@ mod platform { /// short-lived burst of clients, short enough that a wedged or /// preempted pipe surfaces as a clear error rather than a /// silent hang. - const CONNECT_BUSY_TIMEOUT: Duration = Duration::from_secs(5); + const CONNECT_BUSY_TIMEOUT: Duration = Duration::from_secs(2); // Reduced from 5s to 2s pub async fn connect_path(pipe_name: PathBuf) -> Result { let name = pipe_name.to_string_lossy().into_owned(); @@ -221,7 +221,7 @@ mod platform { match ClientOptions::new().open(&name) { Ok(client) => return Ok::(client), Err(err) if err.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => { - sleep(Duration::from_millis(50)).await; + sleep(Duration::from_millis(25)).await; // Reduced from 50ms to 25ms } Err(err) => { return Err(anyhow::Error::from(err)