From 433b9c7e99a65c7d58a773e02f24177cfbdc20d9 Mon Sep 17 00:00:00 2001 From: Sebastien Flory Date: Fri, 18 Sep 2026 18:41:58 +0000 Subject: [PATCH] Tighten dsbx action poll: 500ms initial wait, then 200ms until 2s, then 500ms. Skips the useless immediate GET after 202 and densifies polling while warm tools usually finish. --- cli/dust-sandbox/src/api/client.rs | 47 +++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/cli/dust-sandbox/src/api/client.rs b/cli/dust-sandbox/src/api/client.rs index d7863350af88..903d246cf895 100644 --- a/cli/dust-sandbox/src/api/client.rs +++ b/cli/dust-sandbox/src/api/client.rs @@ -16,7 +16,13 @@ use super::types::{ MCPServerView, SandboxServerViewsResponse, }; -const POLL_INTERVAL: Duration = Duration::from_millis(500); +// Action poll schedule after a 202 create: Temporal + MCP need time before +// the first GET can succeed, so skip the useless immediate check. Then poll +// denser while warm tools usually finish, and ease off for long runners. +const POLL_INITIAL_DELAY: Duration = Duration::from_millis(500); +const POLL_FAST_INTERVAL: Duration = Duration::from_millis(200); +const POLL_FAST_WINDOW: Duration = Duration::from_secs(2); +const POLL_SLOW_INTERVAL: Duration = Duration::from_millis(500); // Hard cap so a wedged action can't pin the CLI forever. const POLL_MAX_DURATION: Duration = Duration::from_secs(10 * 60); const HTTP_REQUEST_TIMEOUT: Duration = Duration::from_secs(30); @@ -29,6 +35,14 @@ const POLL_MAX_CONSECUTIVE_NETWORK_ERRORS: u32 = 30; const POLL_RETRY_BACKOFF_BASE: Duration = Duration::from_millis(500); const POLL_RETRY_BACKOFF_CAP: Duration = Duration::from_secs(5); +fn pending_poll_interval(elapsed: Duration) -> Duration { + if elapsed < POLL_FAST_WINDOW { + POLL_FAST_INTERVAL + } else { + POLL_SLOW_INTERVAL + } +} + const SANDBOX_TOKEN_ENV: &str = "DUST_SANDBOX_TOKEN"; const API_URL_ENV: &str = "DUST_API_URL"; @@ -304,9 +318,14 @@ impl DustApiClient { } async fn poll_action_result(&self, action_id: &str) -> anyhow::Result { - let deadline = Instant::now() + POLL_MAX_DURATION; + let started = Instant::now(); + let deadline = started + POLL_MAX_DURATION; let mut announced = false; let mut consecutive_network_errors: u32 = 0; + + // Fresh creates are never done yet; wait before the first GET. + sleep(POLL_INITIAL_DELAY).await; + loop { let poll_result = self.get_action_status(action_id).await; let response = match poll_result { @@ -355,7 +374,7 @@ impl DustApiClient { eprintln!("waiting on action {action_id}..."); announced = true; } - sleep(POLL_INTERVAL).await; + sleep(pending_poll_interval(started.elapsed())).await; } ActionPollResponse::Rejected => { bail!("action {action_id} was rejected"); @@ -415,7 +434,27 @@ mod tests { use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; - use super::DustApiClient; + use super::{ + pending_poll_interval, DustApiClient, POLL_FAST_INTERVAL, POLL_FAST_WINDOW, + POLL_SLOW_INTERVAL, + }; + + #[test] + fn pending_poll_interval_is_fast_until_two_seconds_then_slow() { + assert_eq!( + pending_poll_interval(Duration::from_millis(500)), + POLL_FAST_INTERVAL + ); + assert_eq!( + pending_poll_interval(POLL_FAST_WINDOW - Duration::from_millis(1)), + POLL_FAST_INTERVAL + ); + assert_eq!(pending_poll_interval(POLL_FAST_WINDOW), POLL_SLOW_INTERVAL); + assert_eq!( + pending_poll_interval(Duration::from_secs(30)), + POLL_SLOW_INTERVAL + ); + } #[tokio::test] async fn frame_database_list_survives_a_cold_start_beyond_the_default_timeout() {