Skip to content

v7.13.4: 并发首次开库不再撞锁(v7.13.3 发布被拦下的原因)+ PR 上跑 3.10–3.13 测试 - #11

Merged
sixgodgit merged 1 commit into
mainfrom
claude/intelligent-hawking-oeyjx8
Oct 9, 2026
Merged

sixgodgit merged 1 commit into
mainfrom
claude/intelligent-hawking-oeyjx8

Conversation

@sixgodgit

Copy link
Copy Markdown
Owner

起因

v7.13.3 的发布流水线(run 37861398183)在 Python 3.11 上失败,构建与发布两步没有执行,PyPI 上没有 7.13.3。

失败的测试是 test_threads_get_distinct_connections:

  • 4 个线程同时第一次打开 ID 中枢库,其中一个在 PRAGMA journal_mode=WAL 上抛 database is locked;
  • 连接设了 30 秒超时也没用:切换日志模式要排他锁,SQLite 发现锁成环时不调用 busy handler,直接报错。

这不只是测试不稳。生产上同样会撞:

  • Hermes 启动时,预热、语义补索引、写入三个线程几乎同时开库;
  • 网关进程和 MCP 进程同时启动时也一样。

同类问题共 7 处

ID 中枢、语义库、全文索引、影子沙、织线、双时态事实、Déjà Vu,各自 connect + 设 WAL + 建表。

本地复现(每轮一个新库,14 个线程在同一瞬间打开):

  • 旧代码 60 轮失败 32 次;
  • 影子沙另有一个竞态:共享连接被两个线程同时创建,报 no such table: trust。

修法

  • 新增 core/sqlite_open.connect(),所有开库都走它:
    • 同一进程内,同一个库文件的「开库 + WAL + 建表」按路径串行;
    • 已经是 WAL 就不再切换;
    • 跨进程撞锁时,回滚后退避重试到超时;
    • 建表函数都是幂等的。
  • 影子沙的共享连接加锁创建。
  • 织线的补列合并进建表函数,只补缺的列。

防复发

  • tests/test_sqlite_open.py:
    • 每个开库入口:多轮,每轮一群线程同一瞬间打开新库;
    • 6 个进程同时打开同一个新库;
    • 重试、不重试、超时放弃三种语义。
    • 在 v7.13.3 的代码上,全入口那项 3 次运行 3 次失败,跨进程那项 3 次中 1 次失败。
  • tests/test_structural_guards.py 新增两项(扫源码):
    • 除 sqlite_open 外不许设 journal_mode;
    • 不许 sqlite3.connect 之后在同一个函数里自己建表。
  • 修正原测试的一个洞:它比较已结束线程的连接 id(),连接被回收后地址会复用。
  • 新增 .github/workflows/ci.yml:每个 PR 和推到 main 时,在 3.10–3.13 上跑全量测试。以前这套矩阵只在发布时才跑。

验证

  • 全量 502 项通过。
  • 3.10 / 3.11 / 3.12 / 3.13 四个干净环境,按发布流水线同样的方式安装后全量通过。
  • 新代码 200 轮 × 14 线程,零失败;并发相关测试连续重复全绿。
  • 3 万条记忆、真实对话节奏下,p50 / p95 为 36 / 53 ms,与 v7.13.3 相同。
  • 真实 Hermes 集成检查通过。

🤖 Generated with Claude Code

https://claude.ai/code/session_019ZZeQFn5F6mEbmX8vPsf4g


Generated by Claude Code

v7.13.3 的发布流水线在 Python 3.11 上失败(构建与发布未执行,PyPI 上没有 7.13.3):
4 个线程同时第一次打开 ID 中枢库,一个在 PRAGMA journal_mode=WAL 上抛 database is locked。
切换日志模式要排他锁,SQLite 发现锁成环时不调用 busy handler,timeout=30 也没用。
生产上 Hermes 启动时的预热 / 语义补索引 / 写入线程、网关进程与 MCP 进程同时启动都会撞上。

同类写法共 7 处(ID 中枢、语义库、全文索引、影子沙、织线、双时态事实、Déjà Vu)。
本地复现:旧代码 60 轮 x 14 线程失败 32 次;影子沙另有共享连接被两个线程同时创建的竞态(no such table: trust)。

- 新增 core/sqlite_open.connect():同一进程内同一库文件的开库 + WAL + 建表按路径串行;
  已是 WAL 不再切换;跨进程撞锁回滚后退避重试到超时;建表函数改为幂等
- 影子沙共享连接加锁创建;织线的补列合并进建表函数,只补缺的列
- tests/test_sqlite_open.py:每个开库入口多轮线程踩踏、多进程同时开同一个新库、重试语义
  (v7.13.3 代码上全入口那项 3/3 失败、跨进程 1/3 失败)
- test_structural_guards:除 sqlite_open 外不许设 journal_mode;不许 connect 后自己建表
- test_threads_get_distinct_connections 修正:比较已结束线程连接的 id() 会碰上地址复用
- .github/workflows/ci.yml:PR 与 main 上跑 3.10–3.13 全量测试,不再等到发布才暴露

验证:全量 502 通过;3.10/3.11/3.12/3.13 干净环境按发布流水线方式安装后全量通过;
新代码 200 轮 x 14 线程零失败;3 万条记忆真实对话节奏 p50/p95 36/53 ms(与 v7.13.3 相同)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZZeQFn5F6mEbmX8vPsf4g
@sixgodgit
sixgodgit merged commit aab72c5 into main Oct 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants