mirror of
https://github.com/openai/codex.git
synced 2026-11-07 16:35:32 +00:00
Flush analytics before in-process app server shutdown (#34831)
## Why The in-process app server can shut down while analytics for a completed turn are still queued, preventing the completed-turn and accepted-line events from being delivered. ## What changed - Add a bounded analytics queue flush that waits for all preceding facts to be processed. - Flush analytics after the in-process runtime drains its outbound work and before acknowledging shutdown. - Extend the in-process shutdown timeout budgets to cover runtime draining and the best-effort analytics flush. ## Testing - Verify that the flush waits for preceding facts and is a no-op when analytics is disabled. - Verify that shutdown waits for delayed analytics delivery, including the completed-turn and accepted-line events produced by a file change. GitOrigin-RevId: 57ca24e535e5e2f3f3202b2e5498f075676c910a
This commit is contained in:
1 parent
b5748e6e3c
commit
88f1cd9664
6 files changed
+435
-8
No files matched your search
@@ -1,6 +1,7 @@
|
||||
use super::AnalyticsEventsClient;
|
||||
use super::AnalyticsEventsDestination;
|
||||
use super::AnalyticsEventsQueue;
|
||||
use super::AnalyticsEventsQueueMessage;
|
||||
#[cfg(debug_assertions)]
|
||||
use super::capture_track_events_request;
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -57,6 +58,8 @@ use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::TurnStatus as AppServerTurnStatus;
|
||||
use codex_app_server_protocol::TurnSteerParams;
|
||||
use codex_app_server_protocol::TurnSteerResponse;
|
||||
#[cfg(debug_assertions)]
|
||||
use codex_login::AuthManager;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use std::collections::HashSet;
|
||||
@@ -71,6 +74,18 @@ use std::time::SystemTime;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl AnalyticsEventsClient {
|
||||
pub(crate) fn new_for_capture_file(auth_manager: Arc<AuthManager>, path: PathBuf) -> Self {
|
||||
Self {
|
||||
queue: Some(AnalyticsEventsQueue::new(
|
||||
auth_manager,
|
||||
AnalyticsEventsDestination::CaptureFile { path },
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_accepted_line_fingerprint_event(thread_id: &str) -> TrackEventRequest {
|
||||
TrackEventRequest::AcceptedLineFingerprints(Box::new(
|
||||
CodexAcceptedLineFingerprintsEventRequest {
|
||||
@@ -197,7 +212,10 @@ fn unique_capture_path(name: &str) -> PathBuf {
|
||||
))
|
||||
}
|
||||
|
||||
fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver<AnalyticsFact>) {
|
||||
fn client_with_receiver() -> (
|
||||
AnalyticsEventsClient,
|
||||
mpsc::Receiver<AnalyticsEventsQueueMessage>,
|
||||
) {
|
||||
let (sender, receiver) = mpsc::channel(8);
|
||||
let queue = AnalyticsEventsQueue {
|
||||
sender,
|
||||
@@ -569,7 +587,8 @@ fn track_request_only_enqueues_analytics_relevant_requests() {
|
||||
client.track_request(/*connection_id*/ 7, request_id, &request);
|
||||
assert!(matches!(
|
||||
receiver.try_recv(),
|
||||
Ok(AnalyticsFact::ClientRequest { .. })
|
||||
Ok(AnalyticsEventsQueueMessage::Fact(input))
|
||||
if matches!(*input, AnalyticsFact::ClientRequest { .. })
|
||||
));
|
||||
}
|
||||
|
||||
@@ -596,7 +615,8 @@ fn track_response_only_enqueues_analytics_relevant_responses() {
|
||||
client.track_response(/*connection_id*/ 7, request_id, response);
|
||||
assert!(matches!(
|
||||
receiver.try_recv(),
|
||||
Ok(AnalyticsFact::ClientResponse { .. })
|
||||
Ok(AnalyticsEventsQueueMessage::Fact(input))
|
||||
if matches!(*input, AnalyticsFact::ClientResponse { .. })
|
||||
));
|
||||
}
|
||||
|
||||
@@ -608,6 +628,36 @@ fn track_response_only_enqueues_analytics_relevant_responses() {
|
||||
assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn flush_waits_for_preceding_fact_delivery() {
|
||||
let (client, mut receiver) = client_with_receiver();
|
||||
client.track_request(
|
||||
/*connection_id*/ 7,
|
||||
RequestId::Integer(1),
|
||||
&sample_turn_start_request(),
|
||||
);
|
||||
|
||||
let flush = tokio::spawn(async move { client.flush().await });
|
||||
assert!(matches!(
|
||||
receiver.recv().await,
|
||||
Some(AnalyticsEventsQueueMessage::Fact(input))
|
||||
if matches!(*input, AnalyticsFact::ClientRequest { .. })
|
||||
));
|
||||
let done_tx = match receiver.recv().await {
|
||||
Some(AnalyticsEventsQueueMessage::Flush(done_tx)) => done_tx,
|
||||
_ => panic!("expected analytics flush barrier"),
|
||||
};
|
||||
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
|
||||
assert!(!flush.is_finished());
|
||||
done_tx.send(()).expect("flush receiver should remain open");
|
||||
flush.await.expect("flush task should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn flush_is_noop_when_analytics_is_disabled() {
|
||||
AnalyticsEventsClient::disabled().flush().await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn track_event_request_batches_only_isolates_accepted_line_fingerprint_events() {
|
||||
let batches = track_event_request_batches(vec![
|
||||
|
||||
Reference in new issue
Block a user