mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +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,3 +1,4 @@
|
||||
use crate::client::AnalyticsEventsClient;
|
||||
use crate::client::AnalyticsEventsQueue;
|
||||
use crate::events::AppServerRpcTransport;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventParams;
|
||||
@@ -174,6 +175,7 @@ use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::SystemTime;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const TEST_PRODUCT_CLIENT_ID: &str = "codex_work_desktop";
|
||||
@@ -1298,6 +1300,110 @@ index 1111111..2222222
|
||||
assert!(event.event_params.line_fingerprints.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(debug_assertions)]
|
||||
async fn analytics_flush_delivers_completed_turn_with_file_diff() {
|
||||
let nonce = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("system clock should be after Unix epoch")
|
||||
.as_nanos();
|
||||
let capture_path = std::env::temp_dir().join(format!(
|
||||
"codex-analytics-turn-flush-{}-{nonce}.jsonl",
|
||||
std::process::id()
|
||||
));
|
||||
let auth_manager = codex_login::AuthManager::from_auth_for_testing(
|
||||
codex_login::CodexAuth::create_dummy_chatgpt_auth_for_testing(),
|
||||
);
|
||||
let client = AnalyticsEventsClient::new_for_capture_file(auth_manager, capture_path.clone());
|
||||
|
||||
for fact in [
|
||||
sample_initialize_fact(/*connection_id*/ 7),
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
response: Box::new(sample_thread_start_response(
|
||||
"thread-2", /*ephemeral*/ false, "gpt-5",
|
||||
)),
|
||||
thread_originator: None,
|
||||
},
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(3),
|
||||
request: Box::new(sample_turn_start_request("thread-2", /*request_id*/ 3)),
|
||||
},
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(3),
|
||||
response: Box::new(sample_turn_start_response("turn-2")),
|
||||
thread_originator: None,
|
||||
},
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::TurnResolvedConfig(Box::new(
|
||||
sample_turn_resolved_config("thread-2", "turn-2"),
|
||||
))),
|
||||
AnalyticsFact::Notification(Box::new(sample_turn_started_notification(
|
||||
"thread-2", "turn-2",
|
||||
))),
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::TurnProfile(Box::new(
|
||||
TurnProfileFact {
|
||||
turn_id: "turn-2".to_string(),
|
||||
profile: sample_turn_profile(),
|
||||
},
|
||||
))),
|
||||
AnalyticsFact::Notification(Box::new(ServerNotification::TurnDiffUpdated(
|
||||
TurnDiffUpdatedNotification {
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
diff: "\
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 1111111..2222222
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -0,0 +1 @@
|
||||
+let value = 1;
|
||||
"
|
||||
.to_string(),
|
||||
},
|
||||
))),
|
||||
AnalyticsFact::Notification(Box::new(sample_turn_completed_notification(
|
||||
"thread-2",
|
||||
"turn-2",
|
||||
AppServerTurnStatus::Completed,
|
||||
/*codex_error_info*/ None,
|
||||
))),
|
||||
] {
|
||||
client.record_fact(fact);
|
||||
}
|
||||
|
||||
client.flush().await;
|
||||
|
||||
let contents = std::fs::read_to_string(&capture_path).expect("read captured analytics events");
|
||||
let event_types = contents
|
||||
.lines()
|
||||
.flat_map(|line| {
|
||||
serde_json::from_str::<serde_json::Value>(line)
|
||||
.expect("parse captured analytics events")["events"]
|
||||
.as_array()
|
||||
.expect("captured events should be an array")
|
||||
.iter()
|
||||
.map(|event| {
|
||||
event["event_type"]
|
||||
.as_str()
|
||||
.expect("captured event type should be a string")
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(event_types.iter().any(|event| event == "codex_turn_event"));
|
||||
assert!(
|
||||
event_types
|
||||
.iter()
|
||||
.any(|event| event == "codex_accepted_line_fingerprints")
|
||||
);
|
||||
|
||||
std::fs::remove_file(capture_path).expect("remove analytics capture file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compaction_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::Compaction(Box::new(CodexCompactionEventRequest {
|
||||
|
||||
Reference in new issue
Block a user