Codex 系统手册

持久化和本地运行时服务启动

stage-66 个文件

这个阶段像开店前先把仓库和账本整理好。程序启动时,会打开本地 SQLite(放在电脑上的小数据库),把旧格式升级成新格式。StateRuntime 统一打开线程、日志、目标、记忆等库;rollout 的 state_db 把会话状态存进去;core 的 bridge 只是递交配置的转接头;migrations 准备升级步骤。若库坏了或被占用,恢复代码会提示用户、挪走坏文件并重建,让后面的功能能放心读写。

本阶段涉及的状态6
  • reg-local-state-runtime本地 SQLite 和 StateRuntime 打开的那套持久化句柄,负责让线程、日志、目标、记忆等数据能长期保存。
  • reg-thread-session-store当前有哪些线程和会话、哪个正在用、哪些能恢复或关闭的共享会话账本。
  • reg-agent-graph多智能体和 fork 线程之间谁带起谁、父子关系是否还活着的关系图。
  • reg-rollout-trace-logrollout 记录下来的可回放流水账和会话状态,用来恢复、排错、重连和事后分析。
  • reg-goal-store系统当前保存和检索的目标、任务意图和目标相关上下文,用于在线程运行、提示词组装和结果更新时持续引用。
  • reg-external-import-state外部聊天记录或会话导入时的来源登记、去重映射、转换进度和已导入标记。

本阶段的文件6

核心启动桥接

这些文件提供顶层入口点,将上层连接到基于 rollout 的本地状态启动。

core/src/state_db_bridge.rs源码 ↗
orchestrationstartup

这个文件解决的是“谁来启动状态数据库”这个小但重要的问题。状态数据库可以理解成一块专门保存运行状态的地方,程序重启后或不同步骤之间可能要靠它记住一些信息。这里并不自己实现数据库,而是把活儿转交给 codex_rollout::state_db,相当于前台窗口收下材料,再交给后厨处理。文件还把 StateDbHandle 重新导出出来;这个 handle 可以理解成“数据库的使用凭证”或“把手”,别的代码拿到它才能继续访问状态数据库。init_state_db 接收整个程序的配置,调用真正的初始化函数,最后返回一个可能存在的数据库把手。如果配置里不需要或不能启用状态数据库,就会返回空。

函数细节1
init_state_db6–8 ↗
async fn init_state_db(config: &Config) -> Option<StateDbHandle>

作用:这个函数用程序配置来尝试启动状态数据库,并把启动后的使用把手交回去。调用者不需要知道状态数据库的具体实现在哪里,只要从这里开始就行。

数据流:进去的是 Config,也就是程序的配置说明。函数把这份配置原样交给外部的 rollout_state_db::init 去做真正的初始化,然后等待结果回来。出来的是 Option<StateDbHandle>:有值表示状态数据库可用,没值表示这次没有可用的状态数据库;它本身不直接改配置,也不自己读写数据库内容。

调用关系:它位于启动流程的接线位置:上层代码在准备好配置后调用它,它再把具体工作交给外部的 init 函数。这样 core 模块只需要依赖这个简单入口,不必到处直接碰 rollout 模块里的状态数据库初始化细节。

调用图:外部调用 1 个(init)。

rollout/src/state_db.rs源码 ↗
orchestrationstartup, request handling, cross-cutting

这个文件解决的是“只靠扫文件太慢、也容易不一致”的问题。程序启动时,它会打开 SQLite 状态库,并等待一次补录完成:也就是把旧 rollout 文件里的会话元数据补进数据库。平时列会话、按 ID 找文件、更新会话时间、修复错误路径时,也会先尝试用数据库。它像一个翻译和门卫:外面的人给它配置、游标、筛选条件、rollout 条目;它把这些换成数据库认识的格式,再交给 codex_state 去真正读写。它也很谨慎:数据库没准备好、补录没完成、路径过期时,不会硬撑,而是记录警告、返回空结果,或回退到扫描文件。

函数细节20
init43–58 ↗
async fn init(config: &impl RolloutConfigView) -> Option<StateDbHandle>

作用:这是最常用的数据库启动入口。它尝试初始化本地状态库;如果失败,不让整个程序崩掉,而是发出启动警告并返回“没有数据库可用”。

数据流:进去的是一份配置视图,里面有 Codex 主目录、SQLite 目录、默认模型提供方等信息。它先把视图变成完整配置,再调用 try_init_with_roots 去打开数据库和做启动补录;成功就返回数据库句柄,失败就打印/记录警告并返回 None。

调用关系:启动阶段会用它来拿到 StateDbHandle。它把真正的初始化工作交给 try_init_with_roots;如果那边出错,它再调用 emit_startup_warning 把问题告诉日志或终端。

调用图:调用 3 个内部函数(from_view, emit_startup_warning, try_init_with_roots);被 1 处调用(state_db_init_backfills_before_returning);外部调用 1 个(format!)。

try_init64–72 ↗
async fn try_init(config: &impl RolloutConfigView) -> anyhow::Result<StateDbHandle>

作用:这是“严格版”的初始化入口。和 init 不同,它不会吞掉错误,适合调用者想把失败原因展示给用户或上层流程的时候用。

数据流:进去的是配置视图。它提取出数据库目录、Codex 主目录、默认模型提供方,然后交给 try_init_with_roots;出来的是成功的数据库句柄,或者带具体原因的错误。

调用关系:测试客户端和 app-server 目标初始化会调用它。它本身不处理补录细节,只是把参数整理好后交给 try_init_with_roots。

调用图:调用 2 个内部函数(from_view, try_init_with_roots);被 2 处调用(start_test_client_with_capacity, init_state_db_for_app_server_target)。

try_init_with_roots74–86 ↗
async fn try_init_with_roots(
    codex_home: PathBuf,
    sqlite_home: PathBuf,
    default_model_provider_id: String,
) -> anyhow::Result<StateDbHandle>

作用:这是按具体路径初始化数据库的包装函数。它把“正常初始化”统一转给内部函数,并使用默认的补录租约设置。

数据流:进去的是 Codex 主目录、SQLite 目录、默认模型提供方。它不改这些内容,只是附上“没有特殊补录租约”的选项,然后调用 try_init_with_roots_inner;出来的是数据库句柄或错误。

调用关系:init 和 try_init 都会走到这里。它存在的意义是把公开入口和更灵活的内部实现隔开,让普通路径更简单。

调用图:调用 1 个内部函数(try_init_with_roots_inner);被 2 处调用(init, try_init)。

try_init_with_roots_and_backfill_lease89–102 ↗
async fn try_init_with_roots_and_backfill_lease(
    codex_home: PathBuf,
    sqlite_home: PathBuf,
    default_model_provider_id: String,
    backfill_lease_seconds: i64,
) -> anyhow::Result<StateDbH

作用:这是测试用的初始化入口,可以指定补录租约时间。补录租约可以理解成“谁有权做这次补录的临时通行证”。

数据流:进去的是两个目录、默认模型提供方,以及租约秒数。它把租约秒数包成可选参数交给 try_init_with_roots_inner;出来的是初始化结果。

调用关系:它只在测试配置下编译,用来模拟补录过程中的竞争或等待情况。真正的初始化动作仍由 try_init_with_roots_inner 完成。

调用图:调用 1 个内部函数(try_init_with_roots_inner)。

try_init_with_roots_inner104–137 ↗
async fn try_init_with_roots_inner(
    codex_home: PathBuf,
    sqlite_home: PathBuf,
    default_model_provider_id: String,
    backfill_lease_seconds: Option<i64>,
) -> anyhow::Result<StateDbHandle

作用:这是数据库启动的核心流程。它负责打开 SQLite 状态运行时,然后确认历史会话已经补进数据库;如果补录没准备好,就关闭数据库并报错。

数据流:进去的是 Codex 主目录、SQLite 目录、默认模型提供方和可选租约。它先调用 codex_state::StateRuntime::init 打开数据库,再调用 wait_for_backfill_gate 等补录完成,并记录这段等待耗时;成功返回数据库句柄,失败则关闭运行时并返回错误。

调用关系:try_init_with_roots 和测试入口都会调用它。它把“等补录完成”的工作交给 wait_for_backfill_gate,并把补录门禁结果交给 codex_state 的统计记录函数。

调用图:调用 2 个内部函数(wait_for_backfill_gate, init);被 2 处调用(try_init_with_roots, try_init_with_roots_and_backfill_lease);外部调用 4 个(now, as_path, clone, record_backfill_gate)。

wait_for_backfill_gate139–201 ↗
async fn wait_for_backfill_gate(
    runtime: &codex_state::StateRuntime,
    codex_home: &Path,
    default_model_provider_id: &str,
    backfill_lease_seconds: Option<i64>,
) -> anyhow::Result<()>

作用:这个函数像启动时的安检口:只有数据库补录状态变成 Complete,程序才继续使用它。这样可以避免数据库只装了一半旧数据就被拿来查。

数据流:进去的是状态运行时、Codex 主目录、默认模型提供方和可选租约。它循环读取补录状态;没完成就触发 metadata 里的补录函数,之后再检查;如果一直没完成直到超时,就返回错误;如果完成,就返回成功。

调用关系:try_init_with_roots_inner 在启动时调用它。它会根据有没有租约,分别把补录工作交给 metadata::backfill_sessions 或 metadata::backfill_sessions_with_lease;等待太久时会通过 emit_startup_warning 提醒用户。

调用图:调用 3 个内部函数(backfill_sessions, backfill_sessions_with_lease, emit_startup_warning);被 1 处调用(try_init_with_roots_inner);外部调用 6 个(now, anyhow!, format!, info!, get_backfill_state, sleep)。

emit_startup_warning203–211 ↗
fn emit_startup_warning(message: &str)

作用:这个函数专门用来发启动警告。它既写日志,也在日志系统还没准备好时直接写到 stderr(标准错误输出,通常就是终端里的错误信息)。

数据流:进去的是一段警告文字。它先调用 warn! 记录日志;如果发现 tracing 日志分发器还没设置,就用 eprintln! 直接打印到终端;没有返回值。

调用关系:init 初始化失败、wait_for_backfill_gate 等待补录时都会用它。它保证早期启动阶段的问题不会因为日志系统没启动而悄悄消失。

调用图:被 2 处调用(init, wait_for_backfill_gate);外部调用 3 个(eprintln!, has_been_set, warn!)。

get_state_db217–244 ↗
async fn get_state_db(config: &impl RolloutConfigView) -> Option<StateDbHandle>

作用:这是“只想读一下数据库,但不负责补录”的打开方式。适合远程 app-server 这类非主控场景:数据库存在且补录完成才用,否则就当没有。

数据流:进去的是配置视图。它先算出数据库文件路径并检查文件是否存在;不存在就记录回退原因并返回 None。存在则尝试打开数据库,再调用 require_backfill_complete 检查补录状态;最后只在一切正常时返回句柄。

调用关系:init_state_db_for_app_server_target 会调用它。它不会调用 metadata 的补录逻辑,而是把“补录是否完成”的判断交给 require_backfill_complete。

调用图:调用 2 个内部函数(require_backfill_complete, init);被 1 处调用(init_state_db_for_app_server_target);外部调用 5 个(record_fallback, state_db_path, model_provider_id, sqlite_home, try_exists)。

sqlite_telemetry_recorder247–252 ↗
fn sqlite_telemetry_recorder(
    metrics: codex_otel::MetricsClient,
    originator: &str,
) -> codex_state::DbTelemetryHandle

作用:这个函数创建一个数据库指标记录器。指标记录器可以理解成“仪表盘探头”,用来统计 SQLite 相关耗时、失败等情况。

数据流:进去的是 OTEL 指标客户端和来源名称。它把这两个值交给 sqlite_metrics::recorder,出来的是 codex_state 能使用的数据库遥测句柄。

调用关系:它是 SQLite 状态库和项目指标系统之间的小接头。真正构造记录器的活儿交给 sqlite_metrics::recorder。

调用图:调用 1 个内部函数(recorder)。

require_backfill_complete254–286 ↗
async fn require_backfill_complete(
    runtime: StateDbHandle,
    codex_home: &Path,
) -> Option<StateDbHandle>

作用:这个函数检查数据库是不是已经完成启动补录。没完成就拒绝使用,避免上层读到不完整的会话列表。

数据流:进去的是数据库运行时句柄和一个用于显示位置的路径。它读取 backfill 状态;如果是 Complete,就原样返回句柄;如果未完成或读取失败,就写警告、记录回退原因,并返回 None。

调用关系:get_state_db 打开数据库后会立刻调用它。它不做补录,只负责判断“这个数据库现在能不能安全读”。

调用图:被 1 处调用(get_state_db);外部调用 3 个(get_backfill_state, record_fallback, warn!)。

cursor_to_anchor288–294 ↗
fn cursor_to_anchor(cursor: Option<&Cursor>) -> Option<codex_state::Anchor>

作用:这个函数把列表分页用的 Cursor(游标,像书签一样表示从哪里继续看)转换成数据库查询用的 Anchor(锚点时间)。

数据流:进去的是可选游标。没有游标就返回 None;有游标就取出时间戳,换算成毫秒,再转成 UTC 时间;转换失败也返回 None,成功则返回数据库锚点。

调用关系:list_thread_ids_db 和 list_threads_db 在分页查询前会调用它。它把上层列表模块的“书签”翻译成 codex_state 能理解的时间条件。

调用图:被 2 处调用(list_thread_ids_db, list_threads_db);外部调用 2 个(from_timestamp_millis, try_from)。

normalize_cwd_for_state_db296–298 ↗
fn normalize_cwd_for_state_db(cwd: &Path) -> PathBuf

作用:这个函数把当前工作目录路径整理成适合数据库比较的样子。这样同一个目录不会因为写法不同,比如大小写或斜杠差异,被当成两个地方。

数据流:进去的是一个路径。它尝试调用 normalize_for_path_comparison 做规范化;如果规范化失败,就退回原路径的拷贝;出来的是用于存入或查询数据库的路径。

调用关系:补录、增量应用 rollout 条目、读修复和重新同步时都会用它。它是所有“按目录筛选/比较会话”能稳定工作的基础小工具。

调用图:被 4 处调用(backfill_sessions_with_lease, apply_rollout_items, read_repair_rollout_path, reconcile_rollout);外部调用 1 个(normalize_for_path_comparison)。

list_thread_ids_db302–352 ↗
async fn list_thread_ids_db(
    context: Option<&codex_state::StateRuntime>,
    codex_home: &Path,
    page_size: usize,
    cursor: Option<&Cursor>,
    sort_key: ThreadSortKey,
    allowed_sources

作用:这个函数只从 SQLite 里列出会话 ID,用来做快速列表或一致性检查。它不扫描 rollout 文件,所以比翻目录更轻。

数据流:进去的是可选数据库上下文、Codex 主目录、分页大小、游标、排序字段、允许的来源、模型提供方过滤、是否只看归档、以及阶段名。它先确认有数据库上下文,把游标、来源枚举、排序字段转换成数据库格式,然后调用 ctx.list_thread_ids;成功返回 ID 列表,失败写警告并返回 None。

调用关系:它处在“列表功能”和底层数据库之间。上层给的是用户能理解的筛选条件,它负责翻译成 codex_state::StateRuntime 的查询参数。

调用图:调用 1 个内部函数(cursor_to_anchor);外部调用 3 个(as_slice, iter, warn!)。

list_threads_db356–450 ↗
async fn list_threads_db(
    context: Option<&codex_state::StateRuntime>,
    codex_home: &Path,
    page_size: usize,
    cursor: Option<&Cursor>,
    sort_key: ThreadSortKey,
    sort_direction: So

作用:这个函数从 SQLite 里列出完整的会话元数据,比如标题、路径、时间、来源等。它支持分页、排序、目录过滤、父会话过滤、归档过滤和搜索。

数据流:进去的是数据库上下文和一组列表筛选条件。它把游标变成锚点,把来源、模型提供方、工作目录、排序方向等整理成 ThreadFilterOptions,然后按是否有父会话 ID 调用不同的数据库查询;成功后还会检查 rollout 路径是否真的还存在,过期的普通列表项会被丢掉并从数据库删除。

调用关系:find_latest_thread_path、list_threads_with_db_fallback、list_rollout_threads 会调用它。它既是数据库查询入口,也是防止“数据库里有但文件已经没了”的一道清理关;路径存在性检查交给 compression::existing_rollout_path。

调用图:调用 1 个内部函数(cursor_to_anchor);被 3 处调用(find_latest_thread_path, list_threads_with_db_fallback, list_rollout_threads);外部调用 5 个(with_capacity, as_slice, iter, existing_rollout_path, warn!)。

find_rollout_path_by_id453–466 ↗
async fn find_rollout_path_by_id(
    context: Option<&codex_state::StateRuntime>,
    thread_id: ThreadId,
    archived_only: Option<bool>,
    stage: &str,
) -> Option<PathBuf>

作用:这个函数按会话 ID 去 SQLite 里找对应的 rollout 文件路径。它让上层不用自己翻目录找文件。

数据流:进去的是可选数据库上下文、会话 ID、是否只查归档、以及阶段名。没有数据库就返回 None;有数据库就调用 ctx.find_rollout_path_by_id;成功返回路径,失败记录警告并返回 None。

调用关系:它是“通过 ID 打开某个会话”时的数据库快捷通道。真正查表的动作由 codex_state::StateRuntime 完成,这里负责容错和日志。

mark_thread_memory_mode_polluted468–483 ↗
async fn mark_thread_memory_mode_polluted(
    context: Option<&codex_state::StateRuntime>,
    thread_id: ThreadId,
    stage: &str,
)

作用:这个函数把某个会话的 memory mode 标记为“被污染”。通俗说,就是记录这个会话的记忆状态可能已经被外部操作弄得不可靠,需要后续谨慎处理。

数据流:进去的是可选数据库上下文、会话 ID 和阶段名。没有数据库就直接结束;有数据库就进入 memories 子数据库,调用 mark_thread_memory_mode_polluted;失败只写警告,不中断主流程。

调用关系:maybe_mark_thread_memory_mode_polluted、mark_thread_memory_mode_polluted_if_external_context、handle_any_tool 会调用它。它把工具执行或外部上下文带来的记忆状态变化,落到数据库的 memories 区域里。

调用图:被 3 处调用(maybe_mark_thread_memory_mode_polluted, mark_thread_memory_mode_polluted_if_external_context, handle_any_tool);外部调用 1 个(warn!)。

reconcile_rollout486–555 ↗
async fn reconcile_rollout(
    context: Option<&codex_state::StateRuntime>,
    rollout_path: &Path,
    default_provider: &str,
    builder: Option<&ThreadMetadataBuilder>,
    items: &[RolloutItem]

作用:这个函数负责把一个 rollout 文件里的会话信息同步进 SQLite。它是“文件世界”和“数据库世界”不一致时的调和员。

数据流:进去的是可选数据库上下文、rollout 路径、默认模型提供方、可选元数据构建器、rollout 条目、归档要求和新的 memory mode。若已经有构建器或条目,就交给 apply_rollout_items 做增量写入;否则它会重新读取 rollout 文件提取元数据,规范化 cwd,保留数据库里已有的 git 信息和明确标题,再按归档参数调整 archived_at,最后 upsert 到数据库并更新 memory mode。

调用关系:列表回退、读修复、线程元数据更新和多项测试都会调用它。它必要时把增量场景交给 apply_rollout_items,把从文件提取元数据交给 metadata::extract_metadata_from_rollout。

调用图:调用 3 个内部函数(extract_metadata_from_rollout, apply_rollout_items, normalize_cwd_for_state_db);被 8 处调用(thread_metadata_update_repairs_loaded_thread_without_resetting_summary, list_threads_with_db_fallback, read_repair_rollout_path, update_thread_metadata_clears_git_info_fields, update_thread_metadata_keeps_archived_thread_archived_in_sqlite, update_thread_metadata_keeps_live_archived_thread_archived_in_sqlite, update_thread_metadata_preserves_memory_mode_when_updating_git_info, update_thread_metadata);外部调用 2 个(is_empty, warn!)。

read_repair_rollout_path558–621 ↗
async fn read_repair_rollout_path(
    context: Option<&codex_state::StateRuntime>,
    thread_id: Option<ThreadId>,
    archived_only: Option<bool>,
    rollout_path: &Path,
)

作用:这个函数在“通过文件扫描找到了正确 rollout 路径”之后,顺手修数据库里的旧路径。它是一种读时修复:读到了问题,就趁机补好。

数据流:进去的是数据库上下文、可选会话 ID、归档要求和正确的 rollout 路径。它先尝试快路径:如果数据库里已有这条元数据,就只改路径、规范化 cwd、调整归档状态,没变化就不写;写失败或找不到行时,走慢路径,从 rollout 文件里读默认模型提供方,再调用 reconcile_rollout 重建并写入。

调用关系:find_thread_path_by_id_str_in_subdir 和 list_threads_with_db_fallback 会调用它。它优先自己修一行数据,修不了时把完整重建交给 reconcile_rollout。

调用图:调用 3 个内部函数(read_session_meta_line, normalize_cwd_for_state_db, reconcile_rollout);被 2 处调用(find_thread_path_by_id_str_in_subdir, list_threads_with_db_fallback);外部调用 2 个(to_path_buf, warn!)。

apply_rollout_items625–666 ↗
async fn apply_rollout_items(
    context: Option<&codex_state::StateRuntime>,
    rollout_path: &Path,
    default_provider: &str,
    builder: Option<&ThreadMetadataBuilder>,
    items: &[RolloutIte

作用:这个函数把新产生的一批 rollout 条目增量写进 SQLite。它适合会话还在继续时边写文件、边更新数据库。

数据流:进去的是数据库上下文、rollout 路径、默认模型提供方、可选元数据构建器、条目列表、阶段名、可选 memory mode 和可选更新时间覆盖值。它先确保有构建器;没有就尝试从条目里生成。然后补上默认模型提供方、设置路径、规范化 cwd,最后调用 ctx.apply_rollout_items 写入;失败时记录警告。

调用关系:reconcile_rollout 在已有构建器或条目时会调用它。它把“从 rollout 条目整理出可写元数据”的准备工作做完,再把实际落库交给 codex_state。

调用图:调用 2 个内部函数(builder_from_items, normalize_cwd_for_state_db);被 1 处调用(reconcile_rollout);外部调用 2 个(to_path_buf, warn!)。

touch_thread_updated_at668–686 ↗
async fn touch_thread_updated_at(
    context: Option<&codex_state::StateRuntime>,
    thread_id: Option<ThreadId>,
    updated_at: DateTime<Utc>,
    stage: &str,
) -> bool

作用:这个函数只更新某个会话的 updated_at 时间。它相当于告诉数据库:“这个会话刚刚有活动,请把最后更新时间改成这个值。”

数据流:进去的是可选数据库上下文、可选会话 ID、更新时间和阶段名。没有数据库或没有 ID 就返回 false;有的话调用 ctx.touch_thread_updated_at;成功返回数据库给出的布尔结果,失败写警告并返回 false。

调用关系:它用于轻量更新,不需要重读整个 rollout 文件。真正更新一列时间的工作由底层 StateRuntime 完成,这里负责检查输入是否齐全和处理失败。

SQLite 运行时初始化

这些文件打开、迁移并组装共享的 SQLite 支持运行时及其迁移策略。

state/src/runtime.rs源码 ↗
orchestrationstartup, cross-cutting, teardown

这个文件解决的是“程序运行时的数据放哪、怎么安全打开”的问题。Codex 不是只在内存里干活,它要记住会话线程、日志、目标、记忆等内容,所以这里把这些内容分成 4 个 SQLite 数据库文件。SQLite 可以理解成一个轻量的本地表格数据库。启动时,StateRuntime::init 会先确保目录存在,再分别打开这些数据库,执行迁移(把旧表结构升级成新表结构),记录启动遥测(像体检报告,告诉我们哪一步成功或失败),最后创建 GoalStore 和 MemoryStore 这类更高层的“专柜”。如果中途某个数据库打不开,它会把已经打开的连接池关掉,避免留下半开半关的状态。这个文件还提供数据库路径查询、完整性检查、清空记忆库等工具。整体上,它像商场开门前的总管:先开门、检查柜台、补齐登记表,再把各柜台交给业务代码使用。

函数细节35
RuntimeDbSpec::path109–111 ↗
fn path(self, codex_home: &Path) -> PathBuf

作用:根据 Codex 的主目录,算出某个运行时数据库文件的完整路径。别人不用自己拼字符串,避免拼错文件名。

数据流:输入是一个主目录路径和这个数据库规格里保存的文件名;它把两者拼在一起;输出就是具体数据库文件应该放在哪里的 PathBuf。

调用关系:它是所有数据库路径计算的小零件。STATE_DB、LOGS_DB、GOALS_DB、MEMORIES_DB 这些规格都会通过它找到自己的文件位置。

调用图:外部调用 1 个(join)。

StateRuntime::init171–178 ↗
async fn init(codex_home: PathBuf, default_provider: String) -> anyhow::Result<Arc<Self>>

作用:这是正常启动状态系统时用的公开入口。调用它后,外部代码会拿到一个可以读写状态、日志、目标和记忆的 StateRuntime。

数据流:输入是 Codex 主目录和默认服务提供方名称;它把这些交给 init_inner,并且不额外传测试遥测;输出是一个共享的 StateRuntime,或者启动失败的错误。

调用关系:很多上层流程都会在需要状态库时调用它。它自己不做细活,而是把真正的打开数据库、迁移、初始化工作交给 StateRuntime::init_inner。

调用图:被 181 处调用(state_runtime, remote_control_state_runtime, remote_control_state_runtime, remote_control_state_runtime, external_agent_config_import_sends_completion_notification_for_sync_only_import, init_state_db, disable_waits_for_in_flight_durable_enable, listen_off_exits_without_persisted_remote_control_enable, listen_off_honors_persisted_remote_control_enable, listen_off_ignores_persisted_enable_when_disabled_by_requirements (+15 more));外部调用 1 个(init_inner)。

StateRuntime::init_with_telemetry_for_tests181–187 ↗
async fn init_with_telemetry_for_tests(
        codex_home: PathBuf,
        default_provider: String,
        telemetry_override: &dyn DbTelemetry,
    ) -> anyhow::Result<Arc<Self>>

作用:这是测试专用的启动入口。它允许测试塞入一个假的遥测收集器,用来确认数据库启动过程中有没有正确上报成功或失败。

数据流:输入是主目录、默认提供方和测试用遥测对象;它把这些传给 init_inner;输出同样是 StateRuntime 或错误。

调用关系:只在测试里使用。它和正式 init 走同一条初始化路径,只是多带了一个可以检查结果的遥测对象。

调用图:被 1 处调用(init_records_successful_sqlite_init_phases_to_explicit_telemetry);外部调用 1 个(init_inner)。

StateRuntime::init_inner189–307 ↗
async fn init_inner(
        codex_home: PathBuf,
        default_provider: String,
        telemetry_override: Option<&dyn DbTelemetry>,
    ) -> anyhow::Result<Arc<Self>>

作用:这是启动 StateRuntime 的核心流程。它负责创建目录、打开 4 个数据库、执行迁移、准备基础数据,并把所有东西组装成运行时对象。

数据流:输入是主目录、默认提供方和可选遥测;它创建目录,拿到各数据库迁移器,依次打开 state、logs、goals、memories 数据库,补齐 backfill_state 这张后台补数据登记表,再读取线程最新更新时间;输出是装好连接池和存储对象的 StateRuntime。若某一步失败,它会关闭前面已经打开的池并返回错误。

调用关系:它是 StateRuntime::init 和测试启动入口背后的总调度。它调用 open_state_sqlite、open_logs_sqlite、open_goals_sqlite、open_memories_sqlite 做数据库打开工作,调用 ensure_backfill_state_row_in_pool 补初始化记录,还创建 GoalStore 和 MemoryStore。

调用图:调用 12 个内部函数(runtime_goals_migrator, runtime_logs_migrator, runtime_memories_migrator, close_sqlite_pools, ensure_backfill_state_row_in_pool, new, new, open_goals_sqlite, open_logs_sqlite, open_memories_sqlite (+2 more));外部调用 9 个(clone, new, new, now, as_path, query_scalar, runtime_state_migrator, create_dir_all, warn!)。

StateRuntime::codex_home310–312 ↗
fn codex_home(&self) -> &Path

作用:返回这个运行时正在使用的 Codex 主目录。需要知道状态文件实际放在哪里的代码会用它。

数据流:它读取 StateRuntime 里保存的 codex_home;不修改任何东西;输出一个路径引用。

调用关系:它是一个简单查询口。测试或辅助代码在种入线程元数据、准备测试线程时会用它确认目录位置。

调用图:被 2 处调用(seed_thread_metadata, upsert_test_thread);外部调用 1 个(as_path)。

StateRuntime::thread_goals314–316 ↗
fn thread_goals(&self) -> &GoalStore

作用:拿到线程目标存储器 GoalStore。外部代码可以通过它读取、设置或清除某个线程的目标。

数据流:它读取 StateRuntime 里已经创建好的 thread_goals 字段;不做数据库操作;输出 GoalStore 的引用。

调用关系:目标相关功能会通过它进入 goals 数据库。它把 StateRuntime 当成总入口,避免外部代码直接碰底层连接池。

调用图:被 4 处调用(clear_thread_goal, get_thread_goal, set_thread_goal, seed_thread_cleanup_state)。

StateRuntime::memories318–320 ↗
fn memories(&self) -> &MemoryStore

作用:拿到记忆存储器 MemoryStore。需要保存、领取、标记记忆任务成功或失败的代码会用它。

数据流:它读取 StateRuntime 中的 memories 字段;不改变状态;输出 MemoryStore 的引用。

调用关系:记忆相关流程会从这里进入 memories 数据库,同时 MemoryStore 也可能配合主 state 数据库使用。

调用图:被 5 处调用(claim, failed, succeed, seed_stage1_output_for_existing_thread, memory_pool)。

StateRuntime::close323–328 ↗
async fn close(&self)

作用:关闭运行时持有的所有数据库连接池。程序退出或测试收尾时要调用它,防止后台连接还占着文件。

数据流:输入是当前 StateRuntime;它依次关闭 MemoryStore、GoalStore、日志库连接池和主状态库连接池;没有返回数据,只完成资源释放。

调用关系:这是 teardown 阶段的收尾动作。它和 init 对应:init 打开所有库,close 把它们按顺序关掉。

调用图:调用 2 个内部函数(close, close)。

StateRuntime::clear_memory_data_in_sqlite_home330–346 ↗
async fn clear_memory_data_in_sqlite_home(sqlite_home: &Path) -> anyhow::Result<bool>

作用:清空某个 SQLite 主目录里的记忆数据。调试命令需要“忘掉记忆但保留其他状态”时会用它。

数据流:输入是 SQLite 主目录;它先算出 memories 数据库路径并检查文件是否存在,不存在就返回 false;存在就打开 memories 数据库,调用清空函数,关闭连接池,最后返回 true。

调用关系:它被调试清理记忆的命令调用。它复用 open_memories_sqlite 打开数据库,并把真正删除数据的动作交给 memories::clear_memory_data_in_pool。

调用图:调用 3 个内部函数(runtime_memories_migrator, clear_memory_data_in_pool, open_memories_sqlite);被 1 处调用(run_debug_clear_memories_command);外部调用 1 个(try_exists)。

close_sqlite_pools349–353 ↗
async fn close_sqlite_pools(pools: &[&SqlitePool])

作用:批量关闭一组 SQLite 连接池。初始化失败时,用它把已经打开的数据库都收好。

数据流:输入是一组连接池引用;它逐个调用 close;没有输出,只释放这些池的后台连接。

调用关系:StateRuntime::init_inner 在某个数据库打开失败或初始化检查失败时调用它,避免前面的成功步骤留下资源泄漏。

调用图:被 1 处调用(init_inner)。

base_sqlite_options355–363 ↗
fn base_sqlite_options(path: &Path) -> SqliteConnectOptions

作用:生成打开 SQLite 数据库时通用的一套设置。这样所有运行时数据库都用同样的安全参数,不会各开各的规矩。

数据流:输入是数据库文件路径;它设置文件名、允许不存在时创建、使用 WAL 日志模式、普通同步级别、5 秒忙等待和关闭 SQL 日志;输出 SqliteConnectOptions。

调用关系:open_sqlite 会先调用它拿基础设置,再加上自动清理空间的选项,然后真正建立连接池。

调用图:被 1 处调用(open_sqlite);外部调用 2 个(from_secs, new)。

open_state_sqlite365–374 ↗
async fn open_state_sqlite(
    path: &Path,
    migrator: &Migrator,
    telemetry_override: Option<&dyn DbTelemetry>,
) -> anyhow::Result<SqlitePool>

作用:打开主状态数据库,也就是保存线程等核心状态的库。它把具体工作交给通用的 open_sqlite。

数据流:输入是 state 数据库路径、迁移器和可选遥测;它带着 STATE_DB 这份规格调用 open_sqlite;输出打开并迁移完成的连接池。

调用关系:StateRuntime::init_inner 启动时首先用它打开主状态库。相关测试也会直接调用它,确认运行时迁移器能接受某些特殊迁移记录。

调用图:调用 1 个内部函数(open_sqlite);被 2 处调用(init_inner, open_state_sqlite_tolerates_newer_applied_migrations)。

open_logs_sqlite376–382 ↗
async fn open_logs_sqlite(
    path: &Path,
    migrator: &Migrator,
    telemetry_override: Option<&dyn DbTelemetry>,
) -> anyhow::Result<SqlitePool>

作用:打开日志数据库。日志单独放一个库,是为了减少它和主状态库抢锁的机会。

数据流:输入是日志数据库路径、迁移器和可选遥测;它带着 LOGS_DB 规格调用 open_sqlite;输出日志库连接池。

调用关系:StateRuntime::init_inner 在主状态库之后调用它。它只是日志库专用外壳,真正打开和迁移由 open_sqlite 完成。

调用图:调用 1 个内部函数(open_sqlite);被 1 处调用(init_inner)。

open_goals_sqlite384–390 ↗
async fn open_goals_sqlite(
    path: &Path,
    migrator: &Migrator,
    telemetry_override: Option<&dyn DbTelemetry>,
) -> anyhow::Result<SqlitePool>

作用:打开目标数据库,也就是保存线程目标和目标统计信息的库。

数据流:输入是 goals 数据库路径、迁移器和可选遥测;它带着 GOALS_DB 规格调用 open_sqlite;输出目标库连接池。

调用关系:StateRuntime::init_inner 用它打开 goals 数据库,然后用这个连接池创建 GoalStore。

调用图:调用 1 个内部函数(open_sqlite);被 1 处调用(init_inner)。

open_memories_sqlite392–398 ↗
async fn open_memories_sqlite(
    path: &Path,
    migrator: &Migrator,
    telemetry_override: Option<&dyn DbTelemetry>,
) -> anyhow::Result<SqlitePool>

作用:打开记忆数据库,也就是保存长期记忆相关数据的库。

数据流:输入是 memories 数据库路径、迁移器和可选遥测;它带着 MEMORIES_DB 规格调用 open_sqlite;输出记忆库连接池。

调用关系:StateRuntime::init_inner 用它创建 MemoryStore;清空记忆数据的调试入口也会用它临时打开 memories 数据库。

调用图:调用 1 个内部函数(open_sqlite);被 2 处调用(clear_memory_data_in_sqlite_home, init_inner)。

open_sqlite400–436 ↗
async fn open_sqlite(
    path: &Path,
    migrator: &Migrator,
    spec: RuntimeDbSpec,
    telemetry_override: Option<&dyn DbTelemetry>,
) -> anyhow::Result<SqlitePool>

作用:这是打开任意运行时 SQLite 数据库的通用流程。它统一负责建连接池、跑迁移、记录遥测和包装错误。

数据流:输入是数据库路径、迁移器、数据库规格和可选遥测;它先生成连接选项,建立最多 5 个连接的连接池,记录“打开”结果,再运行迁移并记录“迁移”结果;成功时输出连接池,失败时关闭池并返回带数据库名称和阶段信息的错误。

调用关系:open_state_sqlite、open_logs_sqlite、open_goals_sqlite、open_memories_sqlite 都只是给它传不同的规格。它是所有运行时数据库真正开门营业的统一关口。

调用图:调用 3 个内部函数(base_sqlite_options, new, record_init_result);被 4 处调用(open_goals_sqlite, open_logs_sqlite, open_memories_sqlite, open_state_sqlite);外部调用 3 个(now, run, new)。

ensure_backfill_state_row_in_pool438–464 ↗
async fn ensure_backfill_state_row_in_pool(
    pool: &sqlx::SqlitePool,
) -> anyhow::Result<()>

作用:确保 backfill_state 表里有一条固定的初始化记录。这个表用来追踪后台补数据任务走到哪里了。

数据流:输入是主状态库连接池;它先查 id=1 的记录是否存在,存在就直接结束;不存在就插入一条状态为 Pending、带当前更新时间的记录;输出为空,只保证数据库里有这条登记。

调用关系:StateRuntime::init_inner 在数据库迁移后调用它。这样后续后台补数据模块不用担心登记表是空的。

调用图:被 1 处调用(init_inner);外部调用 2 个(now, query)。

state_db_filename466–468 ↗
fn state_db_filename() -> String

作用:返回主状态数据库的文件名字符串。需要展示或引用文件名但不想知道常量细节的代码会用它。

数据流:它读取 STATE_DB 规格里的 filename;转换成 String;输出文件名。

调用关系:这是路径辅助函数的一部分,和 state_db_path 配套,让外部代码不用直接访问内部常量。

state_db_path470–472 ↗
fn state_db_path(codex_home: &Path) -> PathBuf

作用:根据 Codex 主目录,返回主状态数据库的完整路径。

数据流:输入是 Codex 主目录;它使用 STATE_DB 的路径规则拼出文件路径;输出 PathBuf。

调用关系:测试会用它找到 state 数据库文件,然后创建数据库或做完整性检查。生产代码也可以用它定位主状态库。

调用图:被 2 处调用(open_state_sqlite_tolerates_newer_applied_migrations, sqlite_integrity_check_reports_ok_for_valid_db)。

logs_db_filename474–476 ↗
fn logs_db_filename() -> String

作用:返回日志数据库的文件名字符串。它让调用者不用记住日志库具体叫什么。

数据流:它读取 LOGS_DB 规格里的 filename;转换成 String;输出文件名。

调用关系:它属于数据库路径工具组,和 logs_db_path 一起给外部代码提供稳定入口。

logs_db_path478–480 ↗
fn logs_db_path(codex_home: &Path) -> PathBuf

作用:根据 Codex 主目录,返回日志数据库的完整路径。

数据流:输入是 Codex 主目录;它把目录和日志库文件名拼起来;输出 PathBuf。

调用关系:外部如果要定位日志数据库文件,会通过这个函数,而不是自己拼接路径。

goals_db_filename482–484 ↗
fn goals_db_filename() -> String

作用:返回目标数据库的文件名字符串。这样文件名只在一个地方定义,减少改名时漏改。

数据流:它读取 GOALS_DB 规格里的 filename;转换成 String;输出文件名。

调用关系:它是目标库路径查询的小工具,服务于需要显示或定位 goals 数据库的代码。

goals_db_path486–488 ↗
fn goals_db_path(codex_home: &Path) -> PathBuf

作用:根据 Codex 主目录,返回目标数据库的完整路径。

数据流:输入是 Codex 主目录;它使用 GOALS_DB 的 path 规则;输出目标库文件路径。

调用关系:它让目标相关的外部工具可以可靠找到 goals 数据库,而不必知道内部文件名常量。

memories_db_filename490–492 ↗
fn memories_db_filename() -> String

作用:返回记忆数据库的文件名字符串。调用者可以用它展示或记录 memories 数据库名称。

数据流:它读取 MEMORIES_DB 规格里的 filename;转换成 String;输出文件名。

调用关系:它和 memories_db_path 配套,是记忆库路径辅助接口的一部分。

memories_db_path494–496 ↗
fn memories_db_path(codex_home: &Path) -> PathBuf

作用:根据 Codex 主目录,返回记忆数据库的完整路径。

数据流:输入是 Codex 主目录;它把目录和 memories 文件名拼接;输出 PathBuf。

调用关系:需要定位记忆数据库文件的代码可以直接调用它。clear_memory_data_in_sqlite_home 内部也用同样的路径规则。

runtime_db_paths498–506 ↗
fn runtime_db_paths(codex_home: &Path) -> Vec<RuntimeDbPath>

作用:一次性列出所有运行时数据库的标签和路径。适合做诊断、备份、错误提示这类需要遍历全部数据库的场景。

数据流:输入是 Codex 主目录;它遍历 4 个数据库规格,为每个规格生成 RuntimeDbPath;输出一个包含 label 和 path 的列表。

调用关系:它把 STATE_DB、LOGS_DB、GOALS_DB、MEMORIES_DB 统一打包给外部使用,避免每个调用者自己维护一份数据库清单。

sqlite_integrity_check509–524 ↗
async fn sqlite_integrity_check(path: &Path) -> anyhow::Result<Vec<String>>

作用:对一个已有 SQLite 数据库文件运行内置完整性检查。它用来判断数据库文件有没有损坏。

数据流:输入是数据库文件路径;它用只读方式打开这个文件,执行 PRAGMA integrity_check,收集返回的字符串列表,然后关闭连接池;输出检查结果,正常通常是 ["ok"]。

调用关系:测试会用它验证一个正常数据库能报告 ok。生产诊断或恢复流程也可以用类似结果判断是否需要处理数据库损坏。

调用图:被 1 处调用(sqlite_integrity_check_reports_ok_for_valid_db);外部调用 2 个(new, new)。

tests::TestTelemetry::counters558–568 ↗
fn counters(&self) -> Vec<MetricEvent>

作用:取出测试遥测对象里记录过的计数事件。测试用它检查初始化阶段是否都上报了成功。

数据流:它锁住内部的 counters 列表,复制每个事件的名称和标签;输出一个新的事件列表,不把内部可变数据直接暴露出去。

调用关系:初始化遥测测试会在 StateRuntime 启动后调用它,然后筛选出 DB_INIT_METRIC 的成功事件来比较阶段集合。

tests::TestTelemetry::counter572–580 ↗
fn counter(&self, name: &str, _inc: i64, tags: &[(&str, &str)])

作用:这是测试版遥测的“记录计数”方法。真正系统可能会上报到监控平台,而这里只是把事件存进内存,方便断言。

数据流:输入是指标名、增加值和标签;它忽略增加值,把标签转换成 map,然后把 MetricEvent 放进受互斥锁保护的列表;输出为空,但内部事件列表多了一条记录。

调用关系:StateRuntime::init_inner 通过 telemetry 记录数据库打开和迁移结果时,会间接调用它。它依赖 tags_to_map 把标签整理成便于比较的形式。

调用图:外部调用 1 个(tags_to_map)。

tests::TestTelemetry::record_duration582–588 ↗
fn record_duration(
            &self,
            _name: &str,
            _duration: std::time::Duration,
            _tags: &[(&str, &str)],
        )

作用:这是测试版遥测的“记录耗时”方法,但这里故意什么都不做。测试只关心成功阶段有没有被计数记录下来。

数据流:输入是指标名、耗时和标签;函数全部忽略;没有输出,也不改变测试对象。

调用关系:它是 DbTelemetry 接口要求必须实现的方法。初始化流程可能会调用记录耗时,但这个测试对象选择不保存这些数据。

tests::tags_to_map591–595 ↗
fn tags_to_map(tags: &[(&str, &str)]) -> BTreeMap<String, String>

作用:把遥测标签从一串键值对转换成 BTreeMap。这样测试比较标签时顺序稳定,不会因为原始数组顺序影响结果。

数据流:输入是标签切片,比如 [("phase", "open_state")];它把每个键和值复制成 String 并放进有序 map;输出这个 BTreeMap。

调用关系:tests::TestTelemetry::counter 调用它来整理标签。后面的测试再通过 map 按键查 status、phase 等字段。

tests::open_db_pool597–605 ↗
async fn open_db_pool(path: &Path) -> SqlitePool

作用:测试里用来打开一个已经存在的 SQLite 数据库连接池。它不创建新文件,适合验证现有数据库状态。

数据流:输入是数据库路径;它创建 SQLite 连接选项,要求文件必须存在,然后连接数据库;输出 SqlitePool,失败时测试直接 panic。

调用关系:迁移兼容性测试用它打开已经插入“未来迁移记录”的数据库,先证明严格迁移器会报错,再测试运行时迁移器能容忍。

调用图:外部调用 2 个(new, connect_with)。

tests::sqlite_integrity_check_reports_ok_for_valid_db608–633 ↗
async fn sqlite_integrity_check_reports_ok_for_valid_db()

作用:这个测试确认:对一个正常的 SQLite 数据库运行完整性检查,会得到 ok。

数据流:它创建临时目录和 state 数据库,建一张简单表,关闭数据库,再调用 sqlite_integrity_check;最后断言结果正好是 ["ok"],并清理临时目录。

调用关系:它直接测试 sqlite_integrity_check 和 state_db_path 的配合,保证诊断工具对健康数据库不会误报。

调用图:调用 3 个内部函数(sqlite_integrity_check, state_db_path, unique_temp_dir);外部调用 6 个(new, connect_with, assert_eq!, query, create_dir_all, remove_dir_all)。

tests::open_state_sqlite_tolerates_newer_applied_migrations636–685 ↗
async fn open_state_sqlite_tolerates_newer_applied_migrations()

作用:这个测试确认运行时使用的 state 迁移器能容忍数据库里已有“比当前代码更新”的迁移记录。这样版本回退或混用时,不会轻易启动失败。

数据流:它创建 state 数据库,先跑当前迁移,再手动插入一个版本号很大的迁移记录;随后证明普通严格迁移器会报 VersionMissing 错误;最后用 open_state_sqlite 和 runtime_state_migrator 打开,断言它能成功。

调用关系:它覆盖 open_state_sqlite 的一个重要兼容行为。测试中也用 state_db_path、open_db_pool 和 runtime_state_migrator 搭出这个场景。

调用图:调用 3 个内部函数(open_state_sqlite, state_db_path, unique_temp_dir);外部调用 9 个(new, connect_with, assert!, query, open_db_pool, runtime_state_migrator, create_dir_all, remove_dir_all, vec!)。

tests::init_records_successful_sqlite_init_phases_to_explicit_telemetry688–727 ↗
async fn init_records_successful_sqlite_init_phases_to_explicit_telemetry()

作用:这个测试确认 StateRuntime 初始化时,会把每个数据库打开、迁移以及后续初始化查询的成功阶段都记录到遥测里。

数据流:它创建临时目录和 TestTelemetry,调用 init_with_telemetry_for_tests 启动运行时;然后从 telemetry 里取出成功的 DB_INIT_METRIC 事件,收集 phase 标签,与预期阶段集合比较;最后关闭连接池并清理目录。

调用关系:它验证 StateRuntime::init_inner 对 record_init_result 的调用是否覆盖完整。TestTelemetry::counter 和 counters 是这个测试观察初始化过程的窗口。

调用图:调用 2 个内部函数(init_with_telemetry_for_tests, unique_temp_dir);外部调用 3 个(assert_eq!, default, remove_dir_all)。

state/src/migrations.rs源码 ↗
configstartup / database initialization

数据库里的表结构会随着程序升级而变化,这些变化通常叫迁移,也就是“把数据库从旧格式搬到新格式”的脚本。这个文件把 state、logs、goals、memories 四类数据各自对应的迁移脚本嵌进程序里,就像把四本说明书随程序一起打包。关键点在 runtime_migrator:它基于原来的迁移器重新做一个运行时版本,并把 ignore_missing 打开。意思是:如果数据库里已经有更高版本程序做过的迁移,当前这个旧程序不要因为“我不认识未来版本”就直接报错;但它仍然会检查自己认识的迁移脚本校验和,防止已知脚本被偷偷改坏。这样做对多版本同时运行很重要,比如一个新程序和一个旧程序短时间并行访问同一个数据库时,旧程序不会因为数据库“走在它前面”而无法启动。

函数细节5
runtime_migrator16–25 ↗
fn runtime_migrator(base: &'static Migrator) -> Migrator

作用:把一套已有的数据库迁移配置改造成“运行时更宽容”的版本。它的主要用途是允许旧程序碰到更新的数据库版本时继续工作,而不是立刻拒绝打开。

数据流:进去的是一个内置的 Migrator,也就是一套数据库升级脚本和执行设置。函数读取里面的迁移列表、锁设置、事务设置、表名和建 schema 设置,原样复制到新的 Migrator;唯一关键变化是把 ignore_missing 设为 true。出来的是一个新的 Migrator,它会继续校验自己认识的脚本,但会忽略数据库里那些它还不认识的更高版本迁移记录。

调用关系:这是四个公开给本模块内部使用的运行时迁移器函数的共同小工厂。runtime_state_migrator、runtime_logs_migrator、runtime_goals_migrator、runtime_memories_migrator 都把各自的基础迁移器交给它,让它统一加上“忽略未来版本”的行为。它内部用 Cow::Borrowed 复用原迁移列表,意思是借用已有数据,不额外复制一大份。

调用图:被 4 处调用(runtime_goals_migrator, runtime_logs_migrator, runtime_memories_migrator, runtime_state_migrator);外部调用 1 个(Borrowed)。

runtime_state_migrator27–29 ↗
fn runtime_state_migrator() -> Migrator

作用:提供 state 数据库用的运行时迁移器。别人需要升级或检查主状态数据库时,会通过它拿到一套更适合实际运行的迁移配置。

数据流:它不接收外部输入,而是使用文件里预先嵌入的 STATE_MIGRATOR。它把这套 state 迁移器交给 runtime_migrator,加上“可以忽略未来迁移版本”的规则。最后返回一个新的 Migrator,供后续数据库初始化或升级使用。

调用关系:它是 state 数据库迁移配置的入口包装。真正改配置的工作交给 runtime_migrator;这个函数本身只是明确告诉调用方:你要的是 state 这一套迁移脚本。

调用图:调用 1 个内部函数(runtime_migrator)。

runtime_logs_migrator31–33 ↗
fn runtime_logs_migrator() -> Migrator

作用:提供 logs 数据库用的运行时迁移器。日志数据库初始化时会用它来确保日志相关表结构是合适的。

数据流:它读取内置的 LOGS_MIGRATOR,也就是 logs_migrations 目录里编译进来的迁移脚本。然后把它交给 runtime_migrator,生成一个能容忍数据库版本更高的 Migrator。输出就是这套日志数据库专用的运行时迁移器。

调用关系:它会被 init_inner 调用,通常发生在数据库初始化流程里。它自己不执行迁移,只负责把“日志数据库该用哪套迁移配置”准备好;具体执行迁移的流程在调用它的初始化代码里继续完成。

调用图:调用 1 个内部函数(runtime_migrator);被 1 处调用(init_inner)。

runtime_goals_migrator35–37 ↗
fn runtime_goals_migrator() -> Migrator

作用:提供 goals 数据库用的运行时迁移器。它让目标相关的数据表可以按当前程序需要被创建或升级,同时不怕数据库已被更新程序提前升级过。

数据流:它使用内置的 GOALS_MIGRATOR,里面来自 goals_migrations 目录。函数把这套基础配置送进 runtime_migrator,得到一个打开 ignore_missing 的新迁移器。结果返回给调用方,用于 goals 数据库的初始化或版本检查。

调用关系:它会在 init_inner 这类初始化流程中被调用。它的位置像一个取工具的抽屉:初始化流程要处理 goals 数据库时,从这里拿迁移器;通用的宽容行为则由 runtime_migrator 统一设置。

调用图:调用 1 个内部函数(runtime_migrator);被 1 处调用(init_inner)。

runtime_memories_migrator39–41 ↗
fn runtime_memories_migrator() -> Migrator

作用:提供 memories 数据库用的运行时迁移器。它用于记忆数据相关表结构的创建、升级,以及某些清理记忆数据的流程中。

数据流:它读取内置的 MEMORIES_MIGRATOR,也就是 memory_migrations 目录里的迁移脚本。随后调用 runtime_migrator,复制原有设置并开启“忽略当前程序不认识的未来迁移版本”。最后返回一个 memories 数据库专用的运行时 Migrator。

调用关系:它会被 init_inner 调用,用在正常初始化阶段;也会被 clear_memory_data_in_sqlite_home 调用,用在清理 SQLite home 里的记忆数据时。它不直接碰数据库内容,而是把正确的迁移工具交给这些更高层流程。

调用图:调用 1 个内部函数(runtime_migrator);被 2 处调用(clear_memory_data_in_sqlite_home, init_inner)。

恢复与操作指引

这些文件处理本地数据库的启动失败恢复,并在持久化初始化出错时提供面向 CLI 的指引。

cli/src/state_db_recovery.rs源码 ↗
orchestrationstartup

Codex 启动时需要打开本地数据库,就像进门前要先找到钥匙。如果数据库坏了、被另一个进程锁住,或者本该是文件夹的位置却变成了一个普通文件,主程序直接崩给用户看会很吓人。这个文件把这些尴尬情况集中处理:先从普通输入输出错误里认出“本地数据库启动错误”,再判断错误细节是“锁住了”还是“损坏了”。如果能自动恢复,它会告诉用户正在把坏文件挪走,调用状态库模块做备份,然后提示数据库已经重建。它还区分有交互终端和没有交互终端的情况:有人在屏幕前时会等用户按回车;脚本环境里则直接继续。底部的测试确认它只备份出问题的数据库文件,也能处理“数据库目录被文件占住”的特殊情况。

函数细节15
startup_error11–14 ↗
fn startup_error(err: &std::io::Error) -> Option<&LocalStateDbStartupError>

作用:这个函数从一个普通的输入输出错误里,看看里面是否藏着“本地数据库启动失败”的具体错误。有人想根据启动失败原因做不同处理时,会先用它把错误认出来。

数据流:输入是一个 std::io::Error,也就是系统读写文件时常见的错误包装。它查看这个错误里面包着的真正错误,如果能确认那是 LocalStateDbStartupError,就把这个更具体的错误借出来;如果不是,就返回空,表示这不是它能处理的数据库启动问题。

调用关系:它处在恢复流程的入口附近,用来把泛泛的启动错误分流到本文件这套数据库恢复提示逻辑。它只向内查看错误内容,不继续调用恢复动作。

调用图:外部调用 1 个(get_ref)。

is_locked16–18 ↗
fn is_locked(detail: &str) -> bool

作用:这个函数判断一段数据库错误说明是不是在说“数据库被锁住了”。锁住通常表示另一个 Codex 还在使用同一份本地数据。

数据流:输入是一段错误文字。它把这段文字交给 codex_state 里的判断函数,由那里按 SQLite 数据库的错误格式识别;输出是 true 或 false,表示是不是锁冲突。

调用关系:它是给上层启动错误处理做分类的小帮手。判断结果通常会决定是否展示“请关掉其他 Codex 再试”的提示。

调用图:外部调用 1 个(sqlite_error_detail_is_lock)。

is_corruption20–22 ↗
fn is_corruption(detail: &str) -> bool

作用:这个函数判断数据库错误说明是不是在说“数据库损坏了”。损坏时,程序可能需要把坏文件移走,再建一份新的数据库。

数据流:输入是一段错误详情文字。它交给 codex_state 的专门识别函数检查,输出一个布尔值:true 表示像是损坏,false 表示不像。

调用关系:它被 is_auto_backup_recoverable 调用,用来决定能不能自动备份并重建数据库。它本身只负责判断,不负责真正备份。

调用图:被 1 处调用(is_auto_backup_recoverable);外部调用 1 个(sqlite_error_detail_is_corruption)。

is_auto_backup_recoverable24–26 ↗
fn is_auto_backup_recoverable(startup_error: &LocalStateDbStartupError) -> bool

作用:这个函数判断一次数据库启动失败是否适合自动恢复。也就是说,程序能不能安全地把问题文件备份走,然后用新数据库继续启动。

数据流:输入是 LocalStateDbStartupError,里面有数据库路径和错误详情。它先看错误详情是不是数据库损坏,再看数据库所在目录是不是被一个普通文件挡住;只要满足其中一种,就输出 true,否则输出 false。

调用关系:它把 is_corruption 和 sqlite_home_is_blocking_file 两种判断合在一起,给启动流程一个简单答案:这次失败是否可以走自动备份恢复路线。

调用图:调用 3 个内部函数(is_corruption, sqlite_home_is_blocking_file, detail)。

sqlite_home_is_blocking_file28–34 ↗
fn sqlite_home_is_blocking_file(startup_error: &LocalStateDbStartupError) -> bool

作用:这个函数检查一个特殊但很麻烦的问题:数据库应该放在某个文件夹里,但那个位置却已经是一个普通文件。就像本来要建一个房间,却发现门口被一块砖堵住了。

数据流:输入是数据库启动错误。它取出数据库路径,找到它的父路径,再读取这个父路径的文件信息;如果发现父路径存在且是普通文件,就返回 true,否则返回 false。

调用关系:它只被 is_auto_backup_recoverable 使用,是自动恢复判断的一部分。它把路径层面的异常也纳入恢复范围,而不只看数据库内容是否损坏。

调用图:调用 1 个内部函数(database_path);被 1 处调用(is_auto_backup_recoverable)。

print_auto_backup_start36–40 ↗
fn print_auto_backup_start(startup_error: &LocalStateDbStartupError)

作用:这个函数在自动备份恢复开始前告诉用户:Codex 没启动起来,是因为本地数据库看起来坏了,现在会把坏数据库挪到一边。

数据流:输入是数据库启动错误。它把几行解释文字打印到标准错误输出,也就是通常显示给用户看的错误通道;然后调用 print_technical_details,把数据库位置和具体原因也打印出来。它不返回实际数据,只产生用户可见的提示。

调用关系:它出现在自动恢复流程的开头。主流程决定要自动备份时,会先用它安抚并告知用户,然后才去执行真正的备份动作。

调用图:调用 1 个内部函数(print_technical_details);外部调用 1 个(eprintln!)。

backup_files_for_fresh_start42–46 ↗
async fn backup_files_for_fresh_start(
    startup_error: &LocalStateDbStartupError,
) -> std::io::Result<Vec<RuntimeDbBackup>>

作用:这个异步函数真正触发备份:把启动失败涉及的数据库文件移到备份位置,好让 Codex 可以用一份新的本地数据库继续启动。异步的意思是它做文件操作时可以等待系统完成,而不把整个程序卡死。

数据流:输入是数据库启动错误。它从错误里取出数据库路径,交给 codex_state::backup_runtime_db_for_fresh_start 去做实际文件搬移;输出是备份记录列表,里面说明原文件在哪里、备份到了哪里。如果文件操作失败,就返回输入输出错误。

调用关系:它是本文件里连接用户提示和底层文件备份的桥。测试 backup_backs_up_only_failed_database_file 和 backup_replaces_blocking_sqlite_home_file 都会调用它,确认它只搬该搬的文件,并能处理路径被文件挡住的情况。

调用图:调用 1 个内部函数(database_path);被 2 处调用(backup_backs_up_only_failed_database_file, backup_replaces_blocking_sqlite_home_file);外部调用 1 个(backup_runtime_db_for_fresh_start)。

confirm_fresh_start_rebuild48–71 ↗
fn confirm_fresh_start_rebuild(
    startup_error: &LocalStateDbStartupError,
    backups: &[RuntimeDbBackup],
) -> std::io::Result<()>

作用:这个函数在数据库已经成功备份并重建后,向用户确认发生了什么。它还会在有人坐在终端前操作时暂停一下,等用户按回车继续。

数据流:输入是启动错误和备份记录列表。它打印数据库路径,并通过 backup_folder 找到备份文件夹;如果标准输入和标准错误都是终端,就读取一行输入等待用户按回车,否则直接提示会继续启动。成功时返回空结果,读取输入失败时返回错误。

调用关系:它位于自动恢复流程的收尾阶段。它调用 backup_folder 把备份列表转换成用户能看懂的备份目录,再根据运行环境决定是等待确认还是直接继续。

调用图:调用 1 个内部函数(backup_folder);外部调用 4 个(new, eprintln!, stderr, stdin)。

print_diagnostic_guidance73–78 ↗
fn print_diagnostic_guidance(startup_error: &LocalStateDbStartupError)

作用:这个函数在不能或不适合自动恢复时,告诉用户下一步该怎么排查。它建议运行 codex doctor,并附上技术细节方便求助。

数据流:输入是数据库启动错误。它打印几行诊断建议,然后调用 print_technical_details 输出数据库位置和具体原因;没有返回数据,只负责给用户显示信息。

调用关系:它通常用于数据库看起来损坏但当前流程没有自动修好的情况。它把“接下来该做什么”和“给别人看哪些信息”一起告诉用户。

调用图:调用 1 个内部函数(print_technical_details);外部调用 1 个(eprintln!)。

print_locked_guidance80–84 ↗
fn print_locked_guidance(startup_error: &LocalStateDbStartupError)

作用:这个函数专门处理数据库被占用的情况。它告诉用户可能还有另一个 Codex 正在运行,需要先关掉再重试。

数据流:输入是数据库启动错误。它把锁冲突的解释打印出来,然后调用 print_technical_details 附上路径和原因;它不改变文件,也不返回额外数据。

调用关系:它通常配合 is_locked 的判断使用。上层发现错误是锁冲突时,会走这里,而不是走损坏恢复或诊断流程。

调用图:调用 1 个内部函数(print_technical_details);外部调用 1 个(eprintln!)。

print_technical_details86–90 ↗
fn print_technical_details(startup_error: &LocalStateDbStartupError)

作用:这个函数统一打印技术细节,避免每个提示函数都重复写同样的内容。细节包括数据库在哪,以及底层错误原因是什么。

数据流:输入是数据库启动错误。它读取错误里的数据库路径和详情文字,打印到标准错误输出;没有返回值,也不修改任何状态。

调用关系:它被 print_auto_backup_start、print_diagnostic_guidance 和 print_locked_guidance 共用。这样不管是自动恢复、诊断建议还是锁冲突提示,用户都能看到同一套关键线索。

调用图:被 3 处调用(print_auto_backup_start, print_diagnostic_guidance, print_locked_guidance);外部调用 1 个(eprintln!)。

backup_folder92–94 ↗
fn backup_folder(backups: &[RuntimeDbBackup]) -> Option<&Path>

作用:这个函数从备份记录里取出备份文件夹路径,方便显示给用户。它只看第一条备份记录,因为同一次恢复的备份通常放在同一个目录里。

数据流:输入是一组 RuntimeDbBackup 备份记录。它取第一条记录,再取这条记录的备份文件路径的父目录;如果列表为空或没有父目录,就返回空。

调用关系:它被 confirm_fresh_start_rebuild 调用,用来把底层的备份文件路径整理成更适合用户阅读的“备份文件夹”。测试 backup_folder_uses_parent_of_first_backup_path 专门确认这个取法没错。

调用图:被 1 处调用(confirm_fresh_start_rebuild);外部调用 1 个(first)。

tests::backup_backs_up_only_failed_database_file104–126 ↗
async fn backup_backs_up_only_failed_database_file() -> std::io::Result<()>

作用:这个测试确认:当某一个数据库文件启动失败时,恢复逻辑只备份那个失败的文件,不会误伤旁边正常的数据库文件。

数据流:测试先建一个临时目录,写入两个数据库文件的假内容:一个代表正常状态库,一个代表失败日志库。然后构造启动错误并调用 backup_files_for_fresh_start;最后检查失败文件已经被移走,正常文件还在,备份文件也确实存在。

调用关系:它直接测试 backup_files_for_fresh_start。这个测试保护了一个重要安全边界:自动恢复不能随便搬走用户本地状态里的其他文件。

调用图:调用 2 个内部函数(backup_files_for_fresh_start, new);外部调用 6 个(new, assert!, assert_eq!, logs_db_path, state_db_path, write)。

tests::backup_replaces_blocking_sqlite_home_file129–145 ↗
async fn backup_replaces_blocking_sqlite_home_file() -> std::io::Result<()>

作用:这个测试确认:如果数据库目录位置被一个普通文件占住,恢复逻辑能识别并处理,让那里重新变成目录。

数据流:测试先创建一个临时路径,并在本该作为数据库目录的位置写入普通文件。然后构造指向该位置下数据库的启动错误,先确认 is_auto_backup_recoverable 认为它可恢复,再调用 backup_files_for_fresh_start;最后检查原位置已经变成目录,挡路的文件被备份走。

调用关系:它同时覆盖 is_auto_backup_recoverable 和 backup_files_for_fresh_start。这个测试保证自动恢复不只会处理数据库内容损坏,也会处理文件系统形状不对的问题。

调用图:调用 2 个内部函数(backup_files_for_fresh_start, new);外部调用 5 个(new, assert!, assert_eq!, state_db_path, write)。

tests::backup_folder_uses_parent_of_first_backup_path148–158 ↗
fn backup_folder_uses_parent_of_first_backup_path()

作用:这个测试确认 backup_folder 返回的是第一条备份文件路径的上级文件夹,而不是文件本身。

数据流:测试手工造一条备份记录,里面有原路径和备份后的完整文件路径。它调用 backup_folder,然后断言结果等于备份文件所在的目录。

调用关系:它直接测试 backup_folder。这样 confirm_fresh_start_rebuild 在给用户显示“备份文件夹”时,就不会误显示成某个具体数据库文件。

调用图:外部调用 2 个(assert_eq!, vec!)。

state/src/runtime/recovery.rs源码 ↗
domain_logic数据库启动和错误恢复阶段

Codex 会把多个运行时 SQLite 数据库放在同一个 SQLite 主目录里。SQLite 是一种本地文件数据库,数据就存在磁盘文件中;它还可能有“-wal”和“-shm”这类配套文件。如果其中一个库损坏,最安全的做法不是直接删除,而是先搬到备份目录,像把坏掉的抽屉从柜子里取出来,保留证据,再让程序重新做一个新抽屉。这个文件就做这件事:先判断错误是不是 SQLite 损坏,再找出是哪一个数据库路径出问题;恢复时会创建一个带时间戳的唯一备份文件夹,把数据库本体和配套文件移动进去。如果 SQLite 主目录本身不是目录,而是挡路的文件,它也会把这个挡路文件整体备份走,再重新建目录。这里还定义了初始化数据库失败时的错误包装,方便之后从一串错误里准确找回出问题的路径。

函数细节17
RuntimeDbInitError::new31–43 ↗
fn new(
        label: &'static str,
        operation: &'static str,
        path: &Path,
        source: anyhow::Error,
    ) -> Self

作用:创建一个“数据库初始化失败”的错误对象。它会记住哪个数据库、做什么操作、在哪个路径失败,以及最底层真正报出的错误。

数据流:输入是数据库标签、操作名称、路径和原始错误 → 它把路径复制成自己保存的一份,并把这些信息打包 → 输出一个 RuntimeDbInitError,之后可以被显示、追踪原因,也可以用来找回坏库路径。

调用关系:打开 SQLite 数据库的流程在失败时会用它包一层更有上下文的错误;测试里也会用它构造错误,确认后续识别逻辑不会被路径里的普通文字误导。

调用图:被 2 处调用(open_sqlite, runtime_db_path_for_corruption_error_ignores_corrupt_word_in_path);外部调用 1 个(to_path_buf)。

RuntimeDbInitError::path45–47 ↗
fn path(&self) -> &Path

作用:取出这个初始化错误里记录的数据库路径。别人用它来知道到底是哪一个数据库文件出问题了。

数据流:输入是一个 RuntimeDbInitError → 它读取内部保存的 PathBuf,并以只读路径形式拿出来 → 输出一个 Path 引用,不改动错误本身。

调用关系:当 runtime_db_path_for_corruption_error 已经确认错误是数据库损坏后,会通过这个方法拿到需要备份的数据库路径。

调用图:外部调用 1 个(as_path)。

RuntimeDbInitError::fmt51–60 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把初始化错误变成一段人能读懂的文字。这样日志或终端里不会只看到生硬的底层错误,而能看到“做什么、哪个库、哪个路径失败”。

数据流:输入是错误对象和格式化输出器 → 它把操作、标签、路径、原始错误拼成一句话 → 输出格式化结果,主要影响错误展示内容。

调用关系:这是 Rust 标准错误显示机制会自动调用的函数;当这个错误被打印、写日志或传给上层展示时,它负责生成文字。

调用图:外部调用 1 个(write!)。

RuntimeDbInitError::source64–66 ↗
fn source(&self) -> Option<&(dyn std::error::Error + 'static)>

作用:告诉错误处理系统:这个错误背后还有一个更底层的原始错误。这样排查问题时可以一路追到真正的 SQLite 或文件系统报错。

数据流:输入是 RuntimeDbInitError → 它取出内部保存的 source 错误引用 → 输出给标准错误链使用,不修改任何数据。

调用关系:Rust 的错误链机制会调用它;本文件里的损坏判断也依赖错误链,逐层查看里面有没有 SQLite 损坏信息。

调用图:外部调用 1 个(as_ref)。

backup_runtime_db_for_fresh_start71–92 ↗
async fn backup_runtime_db_for_fresh_start(
    db_path: &Path,
) -> std::io::Result<Vec<RuntimeDbBackup>>

作用:这是对外使用的主要恢复入口:给它一个数据库路径,它会把这个数据库相关文件备份走,让程序之后可以重新创建干净的新库。

数据流:输入是一个数据库文件路径 → 它先找父目录,也就是 SQLite 主目录;如果主目录是正常目录,就只备份这个数据库和它的配套文件;如果主目录被一个普通文件挡住,就把这个挡路文件整体备份并重建目录;如果目录不存在,就创建目录但返回“没东西可备份” → 输出备份记录列表,或返回文件系统错误。

调用关系:当上层发现某个运行时数据库损坏、准备“清场重建”时会调用它。它自己不搬文件细节,而是根据现场情况把工作交给 backup_runtime_db_files 或 backup_blocking_sqlite_home。

调用图:调用 2 个内部函数(backup_blocking_sqlite_home, backup_runtime_db_files);外部调用 5 个(parent, other, format!, create_dir_all, metadata)。

runtime_db_path_for_corruption_error94–101 ↗
fn runtime_db_path_for_corruption_error(err: &anyhow::Error) -> Option<PathBuf>

作用:从一串错误里判断是否真的发生了 SQLite 数据库损坏,并尽量找出坏掉的数据库路径。

数据流:输入是一个 anyhow::Error,也就是可能包了很多层原因的通用错误 → 它先确认里面有没有 SQLite 损坏信号;有的话,再沿着错误链寻找 RuntimeDbInitError,并取出里面的路径 → 输出 Some(路径) 或 None。

调用关系:它通常用在数据库打开失败之后:上层先拿错误问它“这是坏库吗?坏的是哪个文件?”它会调用 is_sqlite_corruption_error 做总判断,再从 RuntimeDbInitError 里取路径。

调用图:调用 1 个内部函数(is_sqlite_corruption_error);外部调用 1 个(chain)。

is_sqlite_corruption_error103–105 ↗
fn is_sqlite_corruption_error(err: &anyhow::Error) -> bool

作用:判断一个错误链里有没有 SQLite 数据库损坏的迹象。它回答的是一个简单问题:这个失败是不是因为库文件坏了?

数据流:输入是通用错误 → 它逐层查看错误原因,把每一层交给 SQLite 损坏识别函数检查 → 输出 true 或 false,不改动错误。

调用关系:runtime_db_path_for_corruption_error 会先调用它,避免把普通失败误当成数据库损坏。它的具体识别工作交给 sqlite_error_source_is_corruption。

调用图:被 1 处调用(runtime_db_path_for_corruption_error);外部调用 1 个(chain)。

sqlite_error_source_is_corruption107–118 ↗
fn sqlite_error_source_is_corruption(source: &(dyn std::error::Error + 'static)) -> bool

作用:检查错误链里的某一个错误源,看它是不是 SQLite 报出的“数据库坏了”。它只认真正的 sqlx 数据库错误,避免被普通文字误判。

数据流:输入是一层标准错误 → 它先尝试把这层错误看成 sqlx::Error;如果不是数据库错误就返回 false;如果是,就检查错误消息和错误代码 → 输出这一层是否代表 SQLite 损坏。

调用关系:is_sqlite_corruption_error 在遍历错误链时会用它逐个筛查。它会把文字判断交给 sqlite_error_detail_is_corruption,把代码判断交给 sqlite_database_code_is_corruption。

调用图:调用 1 个内部函数(sqlite_error_detail_is_corruption)。

sqlite_database_code_is_corruption120–125 ↗
fn sqlite_database_code_is_corruption(code: Cow<'_, str>) -> bool

作用:判断 SQLite 的错误代码是不是代表数据库损坏。SQLite 可能用数字代码,也可能用名字,所以这里两种都认。

数据流:输入是数据库错误代码文本 → 它统一转成小写后,对照已知损坏代码,比如 11、26、sqlite_corrupt、sqlite_notadb → 输出 true 或 false。

调用关系:sqlite_error_source_is_corruption 在看 sqlx 数据库错误时会用它检查 code 字段。它补充了纯文字消息识别,减少漏判。

调用图:外部调用 1 个(matches!)。

sqlite_error_detail_is_corruption127–137 ↗
fn sqlite_error_detail_is_corruption(detail: &str) -> bool

作用:通过错误消息里的常见说法判断数据库是不是损坏。比如“file is not a database”这种话,对外行来说就是“这个文件不像数据库文件”。

数据流:输入是一段错误详情文字 → 它先转成小写,避免大小写影响判断;再查找多种 SQLite 常见损坏提示和错误码写法 → 输出 true 或 false。

调用关系:sqlite_error_source_is_corruption 会调用它检查数据库错误消息。它负责识别 SQLite 在不同环境下可能给出的不同措辞。

调用图:被 1 处调用(sqlite_error_source_is_corruption)。

sqlite_error_detail_is_lock139–142 ↗
fn sqlite_error_detail_is_lock(detail: &str) -> bool

作用:判断错误消息是不是在说数据库被锁住或正忙。锁住不等于损坏,通常只是有别的连接正在用它。

数据流:输入是一段错误详情文字 → 它转成小写后查找“database is locked”和“database is busy” → 输出 true 或 false,不做恢复动作。

调用关系:它和损坏判断是分开的辅助判断。上层可以用它区分“等一等可能就好”的锁问题和“需要备份重建”的坏库问题。

backup_runtime_db_files144–152 ↗
async fn backup_runtime_db_files(db_path: &Path) -> std::io::Result<Vec<RuntimeDbBackup>>

作用:备份某一个 SQLite 数据库的本体文件和它的配套文件。SQLite 的配套文件可能保存尚未合并的数据,所以不能只搬主文件。

数据流:输入是数据库路径 → 它找出父目录作为 SQLite 主目录,再生成主文件、-wal 文件、-shm 文件这三个候选路径 → 把这些路径交给 backup_sqlite_paths 去实际移动 → 输出被成功备份的文件记录。

调用关系:backup_runtime_db_for_fresh_start 发现 SQLite 主目录是正常目录时会调用它。它负责准备“应该搬哪些文件”的清单,真正搬运交给 backup_sqlite_paths。

调用图:调用 2 个内部函数(backup_sqlite_paths, sqlite_paths);被 1 处调用(backup_runtime_db_for_fresh_start);外部调用 1 个(parent)。

backup_sqlite_paths154–180 ↗
async fn backup_sqlite_paths(
    sqlite_home: &Path,
    paths: impl IntoIterator<Item = PathBuf>,
) -> std::io::Result<Vec<RuntimeDbBackup>>

作用:把给定的一组 SQLite 文件移动到一个新建的备份目录里,并记录每个文件从哪里搬到哪里。

数据流:输入是 SQLite 主目录和一批文件路径 → 它先创建唯一备份目录;然后逐个检查文件是否存在,存在就改名移动到备份目录;每搬一个就记一条 RuntimeDbBackup;如果一个都没搬,就删除空备份目录并报错 → 输出备份记录列表。

调用关系:backup_runtime_db_files 把数据库本体和配套文件清单交给它。它会调用 create_unique_backup_dir 创建安全的目标目录,用 file_name 保留原文件名,再用文件系统 rename 完成移动。

调用图:调用 2 个内部函数(create_unique_backup_dir, file_name);被 1 处调用(backup_runtime_db_files);外部调用 6 个(join, new, other, remove_dir, rename, try_exists)。

backup_blocking_sqlite_home182–200 ↗
async fn backup_blocking_sqlite_home(sqlite_home: &Path) -> std::io::Result<Vec<RuntimeDbBackup>>

作用:处理一种比较尴尬的情况:本该是 SQLite 主目录的位置,竟然已经有一个普通文件挡在那里。它会把这个挡路文件备份走,再把目录建出来。

数据流:输入是 SQLite 主目录路径 → 它找到父目录,生成一个专门的备份父目录名;创建唯一备份目录;把挡路的路径整体移动进去;最后在原位置重新创建目录 → 输出一条备份记录,说明原路径和备份路径。

调用关系:backup_runtime_db_for_fresh_start 发现 SQLite home 不是目录时会调用它。它同样依赖 create_unique_backup_dir 和 file_name 来安全命名备份位置。

调用图:调用 2 个内部函数(create_unique_backup_dir, file_name);被 1 处调用(backup_runtime_db_for_fresh_start);外部调用 5 个(parent, format!, create_dir_all, rename, vec!)。

sqlite_paths202–212 ↗
fn sqlite_paths(db_path: &Path) -> Vec<PathBuf>

作用:根据一个 SQLite 主数据库文件路径,算出它可能对应的三个文件:主文件、-wal 文件、-shm 文件。

数据流:输入是数据库主文件路径 → 它在路径后面分别追加“-wal”和“-shm”形成两个配套文件路径,再加上原路径 → 输出这三个路径组成的列表。

调用关系:backup_runtime_db_files 会调用它来准备备份清单。这样恢复时不会漏掉 SQLite 写入日志和共享内存配套文件。

调用图:被 1 处调用(backup_runtime_db_files);外部调用 2 个(as_os_str, vec!)。

create_unique_backup_dir214–230 ↗
async fn create_unique_backup_dir(backup_parent: &Path) -> std::io::Result<PathBuf>

作用:创建一个不会和旧备份撞名的新备份目录。目录名里带当前时间和序号,像给每次事故备份贴一个时间标签。

数据流:输入是备份父目录 → 它先确保父目录存在;取当前 Unix 时间戳;从序号 0 开始尝试创建 sqlite-时间戳-序号 这样的目录;如果已存在就加一再试 → 输出成功创建的目录路径,或返回创建失败的错误。

调用关系:backup_sqlite_paths 和 backup_blocking_sqlite_home 都靠它拿到安全的备份落脚点。它保证多次恢复不会把以前的备份覆盖掉。

调用图:被 2 处调用(backup_blocking_sqlite_home, backup_sqlite_paths);外部调用 5 个(join, format!, now, create_dir, create_dir_all)。

file_name232–239 ↗
fn file_name(path: &Path) -> std::io::Result<&std::ffi::OsStr>

作用:从一个路径里取出最后的文件名,用来拼接备份目标路径。没有文件名的路径不能安全备份,所以会报错。

数据流:输入是一个路径 → 它尝试读取路径最后一段名字;如果有,就输出这个名字;如果没有,就生成一条说明无法创建备份名的错误 → 不改动磁盘。

调用关系:backup_sqlite_paths 用它保留被搬文件的原名;backup_blocking_sqlite_home 用它给挡路路径和备份父目录命名。

调用图:被 2 处调用(backup_blocking_sqlite_home, backup_sqlite_paths);外部调用 1 个(file_name)。