mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Bound SQLite integrity checks for diagnostic attachments (#40688)
## Why Diagnostic uploads skip the doctor attachment if report generation exceeds its overall timeout. SQLite integrity scans can be delayed by large databases or lock contention. ## What changed - Give each database integrity scan a one-second deadline when collecting a diagnostic attachment. - Interrupt scans and lock waits at the deadline, preserve any corruption rows already returned, and report an incomplete clean scan as a warning. - Keep direct `codex doctor` integrity checks unbounded. ## Testing - Cover interrupted scans, lock waits, retries without a deadline, and corruption results returned before interruption. GitOrigin-RevId: 4817fb459ac53953669a9ba5f03397a6fc82c72e
This commit is contained in:
+49
-36
@@ -162,7 +162,7 @@ const NARROW_TERMINAL_ROWS: u16 = 24;
|
||||
|
||||
/// Options for building a local Codex diagnostic report.
|
||||
///
|
||||
/// The command always runs the full bounded diagnostic set. Human output includes
|
||||
/// The command always runs the full diagnostic set. Human output includes
|
||||
/// detailed diagnostics by default; --summary keeps the terminal output compact.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct DoctorCommand {
|
||||
@@ -170,6 +170,10 @@ pub struct DoctorCommand {
|
||||
#[arg(long, default_value_t = false)]
|
||||
json: bool,
|
||||
|
||||
/// Limit database integrity scans when collecting a feedback attachment.
|
||||
#[arg(long, hide = true, default_value_t = false)]
|
||||
feedback: bool,
|
||||
|
||||
/// Only show grouped check rows and the final count summary.
|
||||
#[arg(long, default_value_t = false)]
|
||||
summary: bool,
|
||||
@@ -452,7 +456,7 @@ async fn build_report(
|
||||
terminal_title_check(config)
|
||||
})
|
||||
},
|
||||
run_async_check("state", progress.clone(), state_check(config)),
|
||||
run_async_check("state", progress.clone(), state_check(config, command)),
|
||||
run_async_check(
|
||||
"thread inventory",
|
||||
progress.clone(),
|
||||
@@ -2179,35 +2183,36 @@ fn non_empty_trimmed(value: String) -> Option<String> {
|
||||
if value.is_empty() { None } else { Some(value) }
|
||||
}
|
||||
|
||||
async fn state_check(config: &Config) -> DoctorCheck {
|
||||
async fn state_check(config: &Config, command: &DoctorCommand) -> DoctorCheck {
|
||||
let mut details = Vec::new();
|
||||
path_readiness(&mut details, "CODEX_HOME", &config.codex_home);
|
||||
path_readiness(&mut details, "log dir", &config.log_dir);
|
||||
path_readiness(&mut details, "sqlite home", config.sqlite_config().home());
|
||||
let mut integrity_failures = Vec::new();
|
||||
let mut status = CheckStatus::Ok;
|
||||
for db in config.sqlite_config().runtime_db_paths() {
|
||||
path_readiness(&mut details, db.label, &db.path);
|
||||
sqlite_integrity_detail(
|
||||
config.sqlite_config(),
|
||||
&mut details,
|
||||
&mut integrity_failures,
|
||||
db.label,
|
||||
&db.path,
|
||||
)
|
||||
.await;
|
||||
// Feedback collection gives each database its own budget; direct runs scan fully.
|
||||
let deadline = command
|
||||
.feedback
|
||||
.then(|| Instant::now() + Duration::from_secs(1));
|
||||
status = status.max(
|
||||
sqlite_integrity_detail(
|
||||
config.sqlite_config(),
|
||||
&mut details,
|
||||
db.label,
|
||||
&db.path,
|
||||
deadline,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
rollout_stats_details(&mut details, &config.codex_home);
|
||||
standalone_release_cache_details(&mut details);
|
||||
|
||||
let status = if integrity_failures.is_empty() {
|
||||
CheckStatus::Ok
|
||||
} else {
|
||||
CheckStatus::Fail
|
||||
};
|
||||
let summary = if status == CheckStatus::Ok {
|
||||
"state paths and databases are inspectable"
|
||||
} else {
|
||||
"state database integrity check failed"
|
||||
let summary = match status {
|
||||
CheckStatus::Ok => "state paths and databases are inspectable",
|
||||
CheckStatus::Warning => "some database integrity checks exceeded their time limit",
|
||||
CheckStatus::Fail => "state database integrity check failed",
|
||||
};
|
||||
let mut check = DoctorCheck::new("state.paths", "state", status, summary).details(details);
|
||||
if status == CheckStatus::Fail {
|
||||
@@ -2221,29 +2226,37 @@ async fn state_check(config: &Config) -> DoctorCheck {
|
||||
async fn sqlite_integrity_detail(
|
||||
sqlite: &codex_state::SqliteConfig,
|
||||
details: &mut Vec<String>,
|
||||
integrity_failures: &mut Vec<String>,
|
||||
label: &str,
|
||||
path: &Path,
|
||||
) {
|
||||
deadline: Option<Instant>,
|
||||
) -> CheckStatus {
|
||||
if !path.is_file() {
|
||||
details.push(format!("{label} integrity: skipped (missing)"));
|
||||
return;
|
||||
return CheckStatus::Ok;
|
||||
}
|
||||
|
||||
match codex_state::sqlite_integrity_check(sqlite, path).await {
|
||||
Ok(rows) if rows.iter().all(|row| row == "ok") => {
|
||||
details.push(format!("{label} integrity: ok"));
|
||||
}
|
||||
Ok(rows) => {
|
||||
let message = format!("{label} integrity: {}", rows.join("; "));
|
||||
integrity_failures.push(message.clone());
|
||||
details.push(message);
|
||||
}
|
||||
let (rows, timed_out) = match codex_state::sqlite_integrity_check(sqlite, path, deadline).await
|
||||
{
|
||||
Ok(codex_state::SqliteIntegrityCheck::Complete(rows)) => (rows, false),
|
||||
Ok(codex_state::SqliteIntegrityCheck::TimedOut(rows)) => (rows, true),
|
||||
Err(err) => {
|
||||
let message = format!("{label} integrity: {err}");
|
||||
integrity_failures.push(message.clone());
|
||||
details.push(message);
|
||||
details.push(format!("{label} integrity: {err}"));
|
||||
return CheckStatus::Fail;
|
||||
}
|
||||
};
|
||||
if timed_out {
|
||||
details.push(format!(
|
||||
"{label} integrity: incomplete (1 second time limit)"
|
||||
));
|
||||
}
|
||||
if rows.iter().any(|row| row != "ok") {
|
||||
details.push(format!("{label} integrity: {}", rows.join("; ")));
|
||||
CheckStatus::Fail
|
||||
} else if timed_out {
|
||||
CheckStatus::Warning
|
||||
} else {
|
||||
details.push(format!("{label} integrity: ok"));
|
||||
CheckStatus::Ok
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user