Apply refreshed cloud config bundles to later sessions (#37908)

## Why

Background refreshes previously warmed only the on-disk cache, so new sessions in the same process continued using the startup snapshot.

## What changed

- Make `CloudConfigBundleLoader` retrieve the latest shared bundle on each configuration load.
- Update the in-memory bundle after successful refreshes while preserving the last successful bundle when a refresh fails.
- Stop refresh work when its loader is replaced or its final clone is dropped.

## Testing

- Cover refreshed requirements and managed configuration in later sessions.
- Cover concurrent initial loads, refresh failures and recovery, bundle clearing, and refresh-task cancellation.

GitOrigin-RevId: ae3d79b575bad71a1eed370cee6dc66dc022ffa8
This commit is contained in:
Abhinav authored and copyberry committed 2026-08-11 01:05:48 +00:00
1 parent 9be95745fb
commit 070a26a1f0
7 files changed
+499 -46

No files matched your search

+29 -16
View File
@@ -1,8 +1,7 @@
//! Cloud config bundle lifecycle orchestration.
//!
//! Startup loads a single shared bundle from cache or backend, and a background
//! refresher keeps the cache warm for future app starts without changing the
//! already-snapshotted runtime config.
//! Startup loads a shared bundle from cache or backend, and background refresh
//! updates both the on-disk cache and the bundle observed by future config loads.
use crate::backend::BundleClient;
use crate::backend::BundleRequestError;
@@ -27,6 +26,8 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use tokio::sync::Mutex;
use tokio::sync::OnceCell;
use tokio::time::sleep;
use tokio::time::timeout;
@@ -77,18 +78,7 @@ pub(crate) struct CloudConfigBundleService<C> {
cache: CloudConfigBundleCache,
codex_home: AbsolutePathBuf,
timeout: Duration,
}
impl<C> Clone for CloudConfigBundleService<C> {
fn clone(&self) -> Self {
Self {
auth_manager: Arc::clone(&self.auth_manager),
client: Arc::clone(&self.client),
cache: self.cache.clone(),
codex_home: self.codex_home.clone(),
timeout: self.timeout,
}
}
latest_bundle: OnceCell<Mutex<Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError>>>,
}
impl<C> CloudConfigBundleService<C>
@@ -108,9 +98,21 @@ where
cache: CloudConfigBundleCache::new(codex_home.clone()),
codex_home,
timeout,
latest_bundle: OnceCell::new(),
}
}
pub(crate) async fn get_latest(
&self,
) -> Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError> {
self.latest_bundle
.get_or_init(|| async { Mutex::new(self.load_startup_bundle_with_timeout().await) })
.await
.lock()
.await
.clone()
}
pub(crate) async fn load_startup_bundle_with_timeout(
&self,
) -> Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError> {
@@ -482,7 +484,12 @@ where
.fetch_remote_bundle_and_update_cache_with_retries(auth, "refresh")
.await
{
Ok(bundle) => emit_load_metric("refresh", "success", bundle.as_ref()),
Ok(bundle) => {
emit_load_metric("refresh", "success", bundle.as_ref());
if let Some(latest_bundle) = self.latest_bundle.get() {
*latest_bundle.lock().await = Ok(bundle);
}
}
Err(err) => {
tracing::error!(
path = %self.cache.path().display(),
@@ -490,6 +497,12 @@ where
"Failed to refresh cloud config bundle cache from remote"
);
emit_load_metric("refresh", "error", /*bundle*/ None);
if let Some(latest_bundle) = self.latest_bundle.get() {
let mut latest_bundle = latest_bundle.lock().await;
if latest_bundle.is_err() {
*latest_bundle = Err(err);
}
}
}
}
true