mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
5935a90619
## Why The app server is long-lived, but its shared model cache otherwise refreshes only when a caller needs it. Once the five-minute cache expires, starting a thread or calling `model/list` can wait for `/models` on the request path. Refresh the cache in the background before it expires so foreground callers normally use fresh local state. ## What changed - Start an app-server worker that refreshes models immediately and then every three minutes using the existing models-manager API. - Hold only a weak reference to the models manager between refreshes, so the worker does not extend its lifetime. - Stop scheduling refreshes when the app-server lifecycle handle is shut down or dropped. A refresh already in progress is allowed to finish. - Adjust affected app-server test fixtures to distinguish the background `/models` probe from the connection they are testing. The existing models-manager cache, refresh strategies, auth handling, ETag behavior, and concurrency semantics are unchanged. ## Testing - `models_refresh_worker::tests::refreshes_immediately_periodically_and_stops_when_dropped` - `suite::v2::remote_control::listen_off_honors_persisted_remote_control_enable` - `suite::v2::attestation::attestation_generate_round_trip_adds_header_to_responses_websocket_handshake`
91 lines
2.7 KiB
Rust
91 lines
2.7 KiB
Rust
use std::sync::Arc;
|
|
use std::sync::atomic::AtomicUsize;
|
|
use std::sync::atomic::Ordering;
|
|
use std::time::Duration;
|
|
|
|
use codex_models_manager::manager::ModelsEndpointClient;
|
|
use codex_models_manager::manager::ModelsEndpointFuture;
|
|
use codex_models_manager::manager::OpenAiModelsManager;
|
|
use codex_models_manager::manager::SharedModelsManager;
|
|
use codex_protocol::error::CodexErr;
|
|
use codex_protocol::error::Result as CoreResult;
|
|
use codex_protocol::openai_models::ModelInfo;
|
|
use pretty_assertions::assert_eq;
|
|
use tempfile::tempdir;
|
|
use tokio::sync::Notify;
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug)]
|
|
struct TestModelsEndpoint {
|
|
fetch_count: AtomicUsize,
|
|
fetched: Notify,
|
|
release_second_fetch: Notify,
|
|
}
|
|
|
|
impl TestModelsEndpoint {
|
|
fn new() -> Arc<Self> {
|
|
Arc::new(Self {
|
|
fetch_count: AtomicUsize::new(0),
|
|
fetched: Notify::new(),
|
|
release_second_fetch: Notify::new(),
|
|
})
|
|
}
|
|
|
|
async fn wait_for_fetch_count(&self, expected: usize) {
|
|
tokio::time::timeout(Duration::from_secs(1), async {
|
|
while self.fetch_count.load(Ordering::SeqCst) < expected {
|
|
self.fetched.notified().await;
|
|
}
|
|
})
|
|
.await
|
|
.unwrap_or_else(|_| panic!("expected {expected} model fetches"));
|
|
}
|
|
}
|
|
|
|
impl ModelsEndpointClient for TestModelsEndpoint {
|
|
fn has_command_auth(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool> {
|
|
Box::pin(async { false })
|
|
}
|
|
|
|
fn list_models<'a>(
|
|
&'a self,
|
|
_client_version: &'a str,
|
|
) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>> {
|
|
Box::pin(async move {
|
|
let fetch_index = self.fetch_count.fetch_add(1, Ordering::SeqCst);
|
|
self.fetched.notify_one();
|
|
if fetch_index == 0 {
|
|
return Err(CodexErr::Io(std::io::Error::other("test failure")));
|
|
}
|
|
if fetch_index == 1 {
|
|
self.release_second_fetch.notified().await;
|
|
}
|
|
Ok((Vec::new(), None))
|
|
})
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn refreshes_immediately_periodically_and_stops_when_dropped() {
|
|
let codex_home = tempdir().expect("temp dir");
|
|
let endpoint = TestModelsEndpoint::new();
|
|
let models_manager: SharedModelsManager = Arc::new(OpenAiModelsManager::new(
|
|
codex_home.path().to_path_buf(),
|
|
endpoint.clone(),
|
|
/*auth_manager*/ None,
|
|
));
|
|
let worker = spawn_with_interval(&models_manager, Duration::from_millis(10));
|
|
|
|
endpoint.wait_for_fetch_count(/*expected*/ 2).await;
|
|
drop(worker);
|
|
endpoint.release_second_fetch.notify_one();
|
|
tokio::time::sleep(Duration::from_millis(30)).await;
|
|
|
|
assert_eq!(endpoint.fetch_count.load(Ordering::SeqCst), 2);
|
|
}
|