diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 40385acfd3..aced91515d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2478,6 +2478,7 @@ name = "codex-async-utils" version = "0.0.0" dependencies = [ "pretty_assertions", + "rand 0.9.3", "tokio", "tokio-util", ] @@ -2696,6 +2697,7 @@ dependencies = [ "base64 0.22.1", "chrono", "codex-agent-identity", + "codex-async-utils", "codex-backend-client", "codex-config", "codex-core", diff --git a/codex-rs/async-utils/Cargo.toml b/codex-rs/async-utils/Cargo.toml index 093bbe0972..eee5fb73b8 100644 --- a/codex-rs/async-utils/Cargo.toml +++ b/codex-rs/async-utils/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true workspace = true [dependencies] +rand = { workspace = true } tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "time"] } tokio-util.workspace = true diff --git a/codex-rs/async-utils/src/backoff.rs b/codex-rs/async-utils/src/backoff.rs new file mode 100644 index 0000000000..78d5b3b41a --- /dev/null +++ b/codex-rs/async-utils/src/backoff.rs @@ -0,0 +1,17 @@ +//! Shared exponential retry delays with jitter. + +use std::time::Duration; + +use rand::Rng; + +const INITIAL_DELAY_MS: u64 = 200; +const BACKOFF_FACTOR: f64 = 2.0; + +/// Return a retry delay starting at 200 ms and doubling on each subsequent attempt, +/// with up to 10% jitter. Attempts zero and one both use the initial delay. +pub fn backoff(attempt: u64) -> Duration { + let exp = BACKOFF_FACTOR.powi(attempt.saturating_sub(1) as i32); + let base = (INITIAL_DELAY_MS as f64 * exp) as u64; + let jitter = rand::rng().random_range(0.9..1.1); + Duration::from_millis((base as f64 * jitter) as u64) +} diff --git a/codex-rs/async-utils/src/lib.rs b/codex-rs/async-utils/src/lib.rs index 16a86229ee..5b93457a64 100644 --- a/codex-rs/async-utils/src/lib.rs +++ b/codex-rs/async-utils/src/lib.rs @@ -1,3 +1,7 @@ +mod backoff; + +pub use backoff::backoff; + use std::future::Future; use tokio_util::sync::CancellationToken; diff --git a/codex-rs/cloud-config/Cargo.toml b/codex-rs/cloud-config/Cargo.toml index ecb33d34be..48ab0d870c 100644 --- a/codex-rs/cloud-config/Cargo.toml +++ b/codex-rs/cloud-config/Cargo.toml @@ -10,10 +10,10 @@ workspace = true [dependencies] base64 = { workspace = true } chrono = { workspace = true, features = ["serde"] } +codex-async-utils = { workspace = true } codex-backend-client = { workspace = true } codex-config = { workspace = true } codex-http-client = { workspace = true } -codex-core = { workspace = true } codex-login = { workspace = true } codex-otel = { workspace = true } codex-protocol = { workspace = true } @@ -27,6 +27,7 @@ tracing = { workspace = true } [dev-dependencies] codex-agent-identity = { workspace = true } +codex-core = { workspace = true } pretty_assertions = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "rt", "test-util", "time"] } diff --git a/codex-rs/cloud-config/src/service.rs b/codex-rs/cloud-config/src/service.rs index 25326c4d5b..29641f14eb 100644 --- a/codex-rs/cloud-config/src/service.rs +++ b/codex-rs/cloud-config/src/service.rs @@ -13,11 +13,11 @@ use crate::metrics::emit_fetch_attempt_metric; use crate::metrics::emit_fetch_final_metric; use crate::metrics::emit_load_metric; use crate::validation::validate_bundle; +use codex_async_utils::backoff; use codex_config::AbsolutePathBuf; use codex_config::CloudConfigBundle; use codex_config::CloudConfigBundleLoadError; use codex_config::CloudConfigBundleLoadErrorCode; -use codex_core::util::backoff; use codex_login::AuthManager; use codex_login::CodexAuth; use codex_login::RefreshTokenError; diff --git a/codex-rs/core/src/util.rs b/codex-rs/core/src/util.rs index 7b9bd97802..8e445aee04 100644 --- a/codex-rs/core/src/util.rs +++ b/codex-rs/core/src/util.rs @@ -1,11 +1,6 @@ -use std::time::Duration; - -use rand::Rng; +pub use codex_async_utils::backoff; use tracing::error; -const INITIAL_DELAY_MS: u64 = 200; -const BACKOFF_FACTOR: f64 = 2.0; - /// Emit structured feedback metadata as key/value pairs. /// /// This logs a tracing event with `target: "feedback_tags"`. If @@ -83,13 +78,6 @@ pub(crate) fn emit_feedback_auth_recovery_tags( ); } -pub fn backoff(attempt: u64) -> Duration { - let exp = BACKOFF_FACTOR.powi(attempt.saturating_sub(1) as i32); - let base = (INITIAL_DELAY_MS as f64 * exp) as u64; - let jitter = rand::rng().random_range(0.9..1.1); - Duration::from_millis((base as f64 * jitter) as u64) -} - pub(crate) fn error_or_panic(message: impl std::string::ToString) { if cfg!(debug_assertions) { panic!("{}", message.to_string());