Move retry backoff into codex-async-utils (#46330)

Move the exponential backoff helper into `codex-async-utils` so
`codex-cloud-config` can use it without a runtime dependency on `codex-core`.
Keep `codex-core` as a development dependency for cloud-config tests.

Preserve the existing retry delays and jitter, and re-export `backoff` from
`codex_core::util` for existing callers.

GitOrigin-RevId: 338f3194e166e77003da532310ff5be78a0eac9e
This commit is contained in:
Michael Bolin
2026-09-18 00:08:19 +00:00
committed by copyberry
parent 608e4cc9a1
commit ff73d63e64
7 changed files with 28 additions and 15 deletions

2
codex-rs/Cargo.lock generated
View File

@@ -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",

View File

@@ -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

View File

@@ -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)
}

View File

@@ -1,3 +1,7 @@
mod backoff;
pub use backoff::backoff;
use std::future::Future;
use tokio_util::sync::CancellationToken;

View File

@@ -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"] }

View File

@@ -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;

View File

@@ -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());