报告 006 · 真实进程与持久化边界
日期:2026-09-19。类型:本机多进程与真实 SQLite 事务实验。15 个预登记场景完成,45 个独立子进程,8 次 SIGKILL,首轮执行通过全部预登记判定;其中包含预期应当暴露错误的负面对照,不能理解为所有策略都正确。没有部署 Kubernetes 或实现产品运行时。
1. 问题、环境与证据
报告 005 使用模型假设描述状态权威与原子效果。本实验将指派复核、去重和实际计数器写入数据库,直接终止工作进程并重新连接检查,检验持久化边界能覆盖哪些故障切点。节点指派属于历史分布式方案,不属于当前 TinyAGI 架构。
| 项目 | 实际记录 |
|---|---|
| Python | 3.14.7,/usr/bin/python3 |
| SQLite | 3.53.4 |
| OS | Linux 6.18.52-1-lts x86_64,glibc 2.44 |
| 数据库模式 | 每个数据库 WAL,连接 synchronous=FULL(数值 2) |
| 故障注入 | 子进程通过标准输入输出停在指定切点,父进程对该子进程发送 SIGKILL,等待退出码 -9 后检查数据库 |
| 隔离 | 每例独立临时目录,实验结束清理;不连接已有服务或集群 |
| 实际效果 | 实验数据库中计数器增加一次;不代表任意网络调用或物理效果 |
experiment.py SHA-256
a2c41340f2cc86a310fae882021f7cb980f314fb6467086e36545d4a0ae45be1
预登记 README.md SHA-256
0c3d52c3dd1c098cb815dc70a88b65dde2600e06acf4462f62f8c4e214dedd0b
2. 指派切换:缓存检查不是提交边界
旧进程先读到 epoch=1 与自身 BootID,停在消息屏障;父进程在数据库条件提交 epoch=2/新 BootID,再恢复旧进程。读上下文时不持有写事务。随后独立新进程提交一次,验证可继续推进。
| 策略 | 改权后的旧状态写入 | 旧 outbox 记录 | 新执行者提交 | 最终状态计数 |
|---|---|---|---|---|
| 使用缓存的指派检查,再无条件提交 | 1 | 1 | 成功 | 2 |
BEGIN IMMEDIATE 内读取当前指派并共同提交 | 0 | 0 | 成功 | 1 |
本例的原子范围是同一个 SQLite 写事务,并非“先读外部分布式锁,再写业务数据库”。结果支持把当前指派复核放在状态/outbox 的同一原子边界内,没有验证复制数据库接管、租约时钟或完整业务约束。
3. 八个崩溃切点:有持久回执仍可重复效果
原子组:计数器与回执在同一数据库事务中提交。分离组:先提交 world 数据库的计数器,再提交 provider 数据库的回执,模拟提供者记录与外部效果不能共同提交的边界。两组的 ResourceEpoch=2 均已预先持久化;本实验不测试安装代号的接管过程。
表中“有/无回执”为崩溃后重新打开数据库的观察,最后一列为新进程重投同键同参数后的计数。
| 策略 | SIGKILL 切点 | 崩溃后效果 | 崩溃后回执 | 重试结果 | 恢复后效果 |
|---|---|---|---|---|---|
| 同一事务 | before_write | 0 | 无 | executed | 1 |
| 同一事务 | effect_written(尚未提交) | 0 | 无 | executed | 1 |
| 同一事务 | receipt_written(尚未提交) | 0 | 无 | executed | 1 |
| 同一事务 | committed_before_reply | 1 | 有 | existing | 1 |
| 分离事务 | before_write | 0 | 无 | executed | 1 |
| 分离事务 | effect_written(效果已提交) | 1 | 无 | executed | 2 |
| 分离事务 | receipt_written(回执已提交) | 1 | 有 | existing | 1 |
| 分离事务 | committed_before_reply | 1 | 有 | existing | 1 |
关键反例是 crash-split-effect_written:磁盘上没有回执,不代表效果未发生。重建 Pod、换 BootID 或挂回同一份回执卷均不能自行消除这个窗口。分离组是在两个真实数据库间检验该反例,没有声称所有外部 API 都会采取相同实现。
八个场景在恢复后都额外检查:epoch=1 携带一个不同业务键被拒绝,避免“旧请求只是碰巧被去重”的假阳性;同一键换参数也均被拒绝。计数器和回执保持不变。这验证的是已保存代号的进程恢复,未验证代号丢失、倒退或高代号接管安装。
4. 并发重投与缺失存储
四次双进程场景各自在两个进程 Ready 后开放入口,争用同一业务键。每次返回一个 executed、一个 existing,计数器均为 1、回执均为 1。操作系统实际调度顺序没有枚举,也未保证锁等待一定发生;只记录这些双进程运行,不推导吞吐或广泛并发保证。
缺失存储场景返回 recovery_required,目录中没有新建文件,也未发生效果。该结果验证脚本明确写出的关闭接纳策略,不能据此认为所有提供者或数据库会自动这样处理。损坏文件、缺表、旧快照和第一次合法初始化仍需分别制定及验证流程。
5. 设计变化与限制
保持当前指派与状态/outbox 共同提交的候选,补充真实进程证据。提供者契约收紧为必须声明:业务键作用域及参数绑定、效果与回执的原子边界、保留期、重启恢复规则和可查询的事实。仅声明“幂等表持久化”不够;没有原子去重或可靠结果核对时保留 unknown,避免恢复时盲重试。
原子计数器组支持受控数据库效果在这四个切点后的单次执行,不能外推为任意外部动作恰好一次。主机 OS 和存储继续运行,故 FULL 配置加 SIGKILL 也没有验证断电耐久性。SQLite 继续只是单机实验载体,分布式权威数据库尚未选定。
实验未覆盖真实复制后端、运行中动作排空、旧备份恢复和非原子提供者的完整核对协议,也未测试 Kubernetes 控制器、CNI/CSI、节点分区、卷迁移或真实业务状态迁移。
数据可用性
本报告公开实验条件、汇总统计和失败分析。原始输入、脚本及逐次运行记录未随报告发布,因此不能仅凭本文独立复现实验。