diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 9b4657f3..a08e084a 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -316,6 +316,7 @@ mod tests { (ErrorCode::NotificationNotFound, "NOTIFICATION_NOT_FOUND"), (ErrorCode::SnapshotNotFound, "SNAPSHOT_NOT_FOUND"), (ErrorCode::PolicyDenied, "POLICY_DENIED"), + (ErrorCode::AppUnresponsive, "APP_UNRESPONSIVE"), (ErrorCode::Internal, "INTERNAL"), ]; for (code, expected) in cases { diff --git a/crates/core/src/ref_action_wait.rs b/crates/core/src/ref_action_wait.rs index 9bd6df0b..b136f66e 100644 --- a/crates/core/src/ref_action_wait.rs +++ b/crates/core/src/ref_action_wait.rs @@ -40,6 +40,11 @@ fn trace_resolve_error(context: &CommandContext, ref_id: &str, err: &AdapterErro pub(crate) const POLL_INTERVAL: Duration = Duration::from_millis(100); pub(crate) const RESOLVE_ATTEMPT: Duration = Duration::from_millis(750); +const MAX_BUDGET_MS: u64 = 24 * 60 * 60 * 1000; + +pub(crate) fn budget_from_ms(ms: u64) -> Duration { + Duration::from_millis(ms.min(MAX_BUDGET_MS)) +} pub(crate) fn execute_with_auto_wait( adapter: &dyn PlatformAdapter, @@ -62,7 +67,7 @@ pub(crate) fn execute_with_auto_wait( ref_id, context, request, - Duration::from_millis(budget_ms), + budget_from_ms(budget_ms), dispatch, ) } diff --git a/crates/core/src/ref_action_wait_tests.rs b/crates/core/src/ref_action_wait_tests.rs index 8b1ba907..102c2ce8 100644 --- a/crates/core/src/ref_action_wait_tests.rs +++ b/crates/core/src/ref_action_wait_tests.rs @@ -8,6 +8,16 @@ use crate::{ use std::sync::atomic::{AtomicU32, Ordering}; use std::time::Duration; +#[test] +fn oversized_timeout_budget_is_clamped_and_never_overflows() { + assert_eq!(budget_from_ms(100), Duration::from_millis(100)); + let clamped = budget_from_ms(u64::MAX); + assert!( + std::time::Instant::now().checked_add(clamped).is_some(), + "deadline construction must not overflow for an oversized --timeout-ms" + ); +} + struct RetryAdapter { resolve_calls: AtomicU32, }