From 792c216c96b6dd1ffa32fbdce14843e08f972535 Mon Sep 17 00:00:00 2001 From: weichen Date: Mon, 28 Sep 2026 09:06:06 +0000 Subject: [PATCH] =?UTF-8?q?docs(docgov):=20=E7=99=BB=E8=AE=B0=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=20map=20=E5=B9=B6=E5=8F=91=E5=B4=A9=E6=BA=83=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=81=E7=BB=8F=E9=AA=8C=E4=B8=8E=E7=AB=9E?= =?UTF-8?q?=E6=80=81=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 登录 500 来自进程被 concurrent map 打崩,不是登录逻辑。单测 -race 已通过,dev 重建仍待合入后复验。 --- .doc/audit/ISSUES.md | 3 +- .doc/ops/CURRENT.md | 1 + .../2026-09-28-docgov-shared-map-race.md | 20 +++++++ .doc/ops/lessons/INDEX.md | 1 + .../2026-09-28-docgov-shared-map-race.md | 59 +++++++++++++++++++ ...9-28-docgov-shared-map-race-test-report.md | 47 +++++++++++++++ 6 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 .doc/ops/lessons/2026-09-28-docgov-shared-map-race.md create mode 100644 .doc/ops/short-term/2026-09-28-docgov-shared-map-race.md create mode 100644 .doc/test-reports/2026-09-28-docgov-shared-map-race-test-report.md diff --git a/.doc/audit/ISSUES.md b/.doc/audit/ISSUES.md index 0e07355b..14b047a6 100644 --- a/.doc/audit/ISSUES.md +++ b/.doc/audit/ISSUES.md @@ -2,10 +2,11 @@ 维护规则:[问题记录与开发经验闭环](../ops/2026-09-23-issue-learning-workflow.md)。每个问题一个稳定 ID;主记录是事实与状态来源,本表为可检索投影。新发现及时登记,修复后保留记录;发布 blocker 仍见 [gap-matrix](../remediation/gap-matrix.md)。 -更新:2026-09-28。只上传文档、无文字时对话 400 已在重载后的 dev 浏览器复验(resolved,`bin/agent-server` 10:22 +0800)。历史任务 run 事件流 HTTP/2 与 502 另记,未在本轮修。激活预览前端预检与管理台密钥 form 已在 dev 浏览器复验(resolved,dev `3e534bbb`,项目 6023)。非编制人触发小队账本提交被 `uow: permission denied` 拒绝(fixing,待 dev 重跑)。test 注册 INTERNAL:标准角色 role-guest 缺失导致 FK 23503,已补种并重启 test(goose 77,resolved)。重复用户名 400 在新代码单测通过;当前 rc 二进制仍返回 INTERNAL,随下次含本提交的 test 制品生效。管理台重置密码入口被 F4-A 误删(端点仍在),已恢复 UI(resolved)。2026-09-27。上传链路:登记并修复上传文档跨端不可访问(占位模板退化 + 元数据/正文不落服务端,fixing 待 dev 集成)。2026-09-26。docgov 批准链:登记并修复 review_snapshot_id 装载缺失与 uow 行锁自死锁(resolved);memDocs 持久化失败缓存投毒(investigating)。历史 resolved 不表示本日产品回归通过。系统 code review P0/P1 批次修复落账(5 resolved + 1 deferred + 1 open,主记录见 [整改文档](2026-09-26-system-code-review-fixes.md),来源 [审计](2026-09-26-system-code-review.md))。 +更新:2026-09-28。docgov 共享 map 并发读写曾在 06:38 UTC 打崩 dev 进程,登录等 API 在进程缺席期间 500(verifying,单测 `-race` 已过,dev 重建未做)。只上传文档、无文字时对话 400 已在重载后的 dev 浏览器复验(resolved,`bin/agent-server` 10:22 +0800)。历史任务 run 事件流 HTTP/2 与 502 另记,未在本轮修。激活预览前端预检与管理台密钥 form 已在 dev 浏览器复验(resolved,dev `3e534bbb`,项目 6023)。非编制人触发小队账本提交被 `uow: permission denied` 拒绝(fixing,待 dev 重跑)。test 注册 INTERNAL:标准角色 role-guest 缺失导致 FK 23503,已补种并重启 test(goose 77,resolved)。重复用户名 400 在新代码单测通过;当前 rc 二进制仍返回 INTERNAL,随下次含本提交的 test 制品生效。管理台重置密码入口被 F4-A 误删(端点仍在),已恢复 UI(resolved)。2026-09-27。上传链路:登记并修复上传文档跨端不可访问(占位模板退化 + 元数据/正文不落服务端,fixing 待 dev 集成)。2026-09-26。docgov 批准链:登记并修复 review_snapshot_id 装载缺失与 uow 行锁自死锁(resolved);memDocs 持久化失败缓存投毒(investigating)。历史 resolved 不表示本日产品回归通过。系统 code review P0/P1 批次修复落账(5 resolved + 1 deferred + 1 open,主记录见 [整改文档](2026-09-26-system-code-review-fixes.md),来源 [审计](2026-09-26-system-code-review.md))。 | issue_id | 模块 / 检索词 | 问题 | 严重度 | issue_state | owner | 证据日期 / 范围 | 唯一主记录 | 经验 | |---|---|---|---|---|---|---|---|---| +| ISSUE-20260928-docgov-map-race | docgov / memDocs / concurrent map / GetDocument / 登录 500 | per-doc 锁拆分后共享 map 无互斥,不同文档并发读写 fatal,进程退出期间全部 API 500 | critical | verifying | Cursor | 2026-09-28;`.tmp/launch/server.log` fatal + `web.log` ECONNREFUSED;`TestMemMapsNoRaceAcrossDocs` `-race` PASS(`a90cce73`);dev 重建未做 | [主记录](../ops/short-term/2026-09-28-docgov-shared-map-race.md#issue-20260928-docgov-map-race) | [LESSON-20260928-docgov-shared-map-race](../ops/lessons/2026-09-28-docgov-shared-map-race.md) | | ISSUE-20260928-quality-heading-drift | squad / quality_gate / 缺少必填章节 / State-of-the-Art / H3 | 调研报告章节在,只因 H3 和少空格被判缺章,受控文档不投影 | high | verifying | Cursor | 2026-09-28;dev `research_1790566833205199368` manifest score 85;单测已覆盖,dev 重跑未做 | [主记录](../ops/short-term/2026-09-28-quality-gate-heading-mismatch.md#issue-20260928-quality-heading-drift) | [LESSON-20260928-quality-heading-normalize](../ops/lessons/2026-09-28-quality-heading-normalize.md) | | ISSUE-20260928-data-dir-doubled | workspace / data/dev/data/dev / DataDir | 出现双层 `data/dev/data/dev/workspaces` 且只有孤立 output.md,写入点未证实 | medium | open | — | 2026-09-28;`intake_1790565513115586191` 双路径对照 | [主记录](../ops/short-term/2026-09-28-quality-gate-heading-mismatch.md#issue-20260928-data-dir-doubled) | 本轮不修 | | ISSUE-20260928-progress-query-trigger | intent / matchTriggerTask / QUERY_PROGRESS / 编写进度 | 询问调研报告编写进度时,写动词加任务名词被识别成启动新任务,进度回复不读真实 run | medium | verifying | Cursor | 2026-09-28;「调研报告编写进度怎么样了」单测为 QUERY_PROGRESS;「开始做调研报告」仍为 research。浏览器复验见测试报告 | [主记录](../ops/short-term/2026-09-28-refresh-task-state-recovery.md) | 疑问进度句必须让行查询;显式「开始/重新」仍启动任务 | diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index a511cdcb..dbd77238 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -18,6 +18,7 @@ | owner | task | branch | worktree | mutex | status | |---|---|---|---|---|---| +| Cursor | docgov-shared-map-race-2026-09-28-r2 | fix/docgov-shared-map-race-2026-09-28 | .worktrees/docgov-shared-map-race-2026-09-28 | docgov-shared-map-race | in_progress | | Cursor | promote-rc-beta6-closeout-2026-09-28 | feature/promote-rc-beta6-closeout-2026-09-28 | .worktrees/promote-rc-beta6-closeout-2026-09-28 | git:test+env:test | done | | Cursor | quality-gate-heading-mismatch-2026-09-28-r3 | fix/quality-gate-heading-mismatch-2026-09-28 | .worktrees/quality-gate-heading-mismatch-2026-09-28 | quality-gate-heading | in_progress | | Cursor | agent-reply-llm-narrative-2026-09-28 | feature/agent-reply-llm-narrative-2026-09-28 | .worktrees/agent-reply-llm-narrative-2026-09-28 | chat+coordinator | in_progress | diff --git a/.doc/ops/lessons/2026-09-28-docgov-shared-map-race.md b/.doc/ops/lessons/2026-09-28-docgov-shared-map-race.md new file mode 100644 index 00000000..6c2baac0 --- /dev/null +++ b/.doc/ops/lessons/2026-09-28-docgov-shared-map-race.md @@ -0,0 +1,20 @@ +--- +lesson_id: LESSON-20260928-docgov-shared-map-race +lesson_state: candidate +owner: Cursor +reviewer: Cursor(自审) +last_reviewed: 2026-09-28 +reviewed_revision: a90cce73 +source_issues: [ISSUE-20260928-docgov-map-race] +keywords: [docgov, memDocs, memMu, per-doc lock, concurrent map, 登录 500] +--- + +# 拆细粒度锁后,共享 map 仍要单独互斥 + +- 触发条件:把全局锁拆成 per-id 锁,但多个 id 仍读写同一张 Go map(缓存、审计、批注、提案)。 +- 机制与来源:docgov 的 per-doc 锁不保护 `memDocs` 等 map。不同文档并发时 `GetDocument` 与写入撞上,运行时 fatal,进程退出后所有 HTTP API(含登录)500。证据:2026-09-28 `.tmp/launch/server.log` 与 `web.log` 的 `ECONNREFUSED`。修复见 [任务](../short-term/2026-09-28-docgov-shared-map-race.md)。 +- 开发检查:map 的读写、遍历、append 都走同一把锁。已持有外层锁的路径不能再锁同一把(Go 互斥锁不可重入)。锁序固定,禁止在 map 锁内再拿文档锁或外层锁。不要用「登录 500」去改登录代码。 +- 验证方式:不同 docID 并发创建与读取,`go test -race` 覆盖 `TestMemMapsNoRaceAcrossDocs`。进程崩溃类问题还要看服务日志里的 fatal,而不是只看单个 API 状态码。 +- 适用边界:适用于 docgov 这四张内存 map。文档结构体字段的数据竞争、以及持久化失败后的缓存投毒(`ISSUE-20260926-docgov-memdocs-poison`)不在本条范围内。 +- 防护落实:`memMu` 与 `TestMemMapsNoRaceAcrossDocs`。没有新增静态检查,漏一个裸访问仍要靠 `-race` 和复查。 +- 修订与失效:若这四张 map 改为分片或移除,本条对那个存储失效,保留历史。 diff --git a/.doc/ops/lessons/INDEX.md b/.doc/ops/lessons/INDEX.md index 31de46ed..a7360e5f 100644 --- a/.doc/ops/lessons/INDEX.md +++ b/.doc/ops/lessons/INDEX.md @@ -4,6 +4,7 @@ | lesson_id | 触发条件 / 关键词 | 注意事项 | lesson_state | 主记录 / 最近复核 | |---|---|---|---|---| +| LESSON-20260928-docgov-shared-map-race | docgov、memDocs、per-doc 锁、concurrent map、登录 500 | 拆锁后共享 map 仍要单独互斥;锁不可重入;全站 500 先看进程是否退出 | candidate | [共享 map 互斥](2026-09-28-docgov-shared-map-race.md);2026-09-28 | | LESSON-20260928-quality-heading-normalize | 缺少必填章节、State-of-the-Art、H3、质量门禁、AutoRepair | 标题比对去空白并跨 H2–H4;层级漂移不扣分;真实缺章仍失败 | candidate | [章节标题归一化](2026-09-28-quality-heading-normalize.md);2026-09-28 | | LESSON-20260928-task-write-verb-visible-refusal | 帮我定、制定、ClarificationQuestion、前置条件、quality_failed | 写动词覆盖制定与帮我定;拒绝和澄清进对话;质量失败不写宿主机路径 | candidate | [写动词与可见拒绝](2026-09-28-task-write-verb-visible-refusal.md);2026-09-28 | | LESSON-20260928-activation-preview-client-gate | activation-preview、NO_GOVERNED_DOCUMENT、clientDutyIssues、400 | 服务端激活门禁要有同等前端预检;live_edit 不套激活门禁 | validated | [激活预检对齐](2026-09-28-activation-preview-client-gate.md);2026-09-28 | diff --git a/.doc/ops/short-term/2026-09-28-docgov-shared-map-race.md b/.doc/ops/short-term/2026-09-28-docgov-shared-map-race.md new file mode 100644 index 00000000..9f3a1b5c --- /dev/null +++ b/.doc/ops/short-term/2026-09-28-docgov-shared-map-race.md @@ -0,0 +1,59 @@ +--- +title: "docgov 共享 map 并发读写导致进程崩溃" +status: in_progress +created: 2026-09-28 +owner: Cursor +task_id: docgov-shared-map-race-2026-09-28-r2 +policy_revision: issue-learning-2026-09-23-r1 @ a3db288a +review_state: pending +integration_state: pending +deployment: pending +cleanup: pending +remote_sync: not_requested +updated: 2026-09-28 +write_scope: internal/docgov .doc/ops/CURRENT.md .doc/ops/short-term/2026-09-28-docgov-shared-map-race.md .doc/test-reports/2026-09-28-docgov-shared-map-race-test-report.md .doc/audit/ISSUES.md .doc/ops/lessons/2026-09-28-docgov-shared-map-race.md .doc/ops/lessons/INDEX.md +mutex: docgov-shared-map-race +base_dev_sha: a3db288a7aa055ff5884e3aa4d91511959cff721 +--- + +# docgov 共享 map 并发读写 + +## 范围与授权 + +用户报告 dev(`frp-mix.com:58880`)登录及多个 API 返回 500,并说明没有改登录代码。授权是查清原因并修复。允许写路径见 front matter。交付终点:共享 map 并发读写有回归测试,合入 local dev 后重建并复验 `/health` 与登录接口不再因进程死亡返回 500。不改登录协议,不晋升 test/main,不 push。 + +## 问题登记与经验检索 + +- issue_id: `ISSUE-20260928-docgov-map-race` +- 检索词:`docgov`、`memDocs`、`concurrent map`、`500`、`登录` +- 命中:`ISSUE-20260926-docgov-memdocs-poison`(同模块内存态与库分叉,机制不同,本轮不关)。无「per-doc 锁拆分后共享 map 无互斥」的既有经验。 +- 采纳:修共用 map 互斥,不用登录特例。 + +## 现象 + +2026-09-28 06:38 UTC,dev `agent-server` 在 `GetDocument`(`internal/docgov/engine_read.go`)触发 `fatal error: concurrent map read and map write` 后退出。06:38–06:49 Vite 代理 `ECONNREFUSED 127.0.0.1:8080`,登录、`auth/me`、通知、维护状态等全部 500。06:49 进程被重新拉起后接口恢复。登录代码未改。 + +## 根因 + +`340342fa` 把 docgov 全局锁拆成 per-doc 锁。per-doc 锁只串行同一 `docID`。`memDocs`、`memAudits`、`memComms`、`memProps` 仍被不同文档并发读写。`e.mu` 不是可重入锁,且部分调用方已经持有它,所以新增 `memMu` 专门保护这四张 map。锁序:docLocks → `e.mu` → `memMu`。 + +## 修复 + +`a90cce73`。回归:`TestMemMapsNoRaceAcrossDocs`(`go test -race`)。 + +## 五态 + +| 字段 | 值 | +|---|---| +| review_state | pending | +| integration_state | pending | +| deployment | pending(合入后需重建 dev `agent-server`) | +| cleanup | pending | +| remote_sync | not_requested | + +## ISSUE-20260928-docgov-map-race + +- 预期:文档治理并发不杀死 HTTP 进程;登录等无关 API 保持可用。 +- 实际:2026-09-28 06:38 UTC 进程 fatal,约 11 分钟内全部 API 500。 +- 状态:verifying(单测已过;dev 重建复验尚未做)。 +- 同类:所有裸读写上述四张 map 的路径,已收口到 `memMu` 辅助函数。`ISSUE-20260926-docgov-memdocs-poison` 仍是另一机制。 diff --git a/.doc/test-reports/2026-09-28-docgov-shared-map-race-test-report.md b/.doc/test-reports/2026-09-28-docgov-shared-map-race-test-report.md new file mode 100644 index 00000000..cd4319c0 --- /dev/null +++ b/.doc/test-reports/2026-09-28-docgov-shared-map-race-test-report.md @@ -0,0 +1,47 @@ +# docgov 共享 map 并发读写 测试报告 + +## 一、 测试背景 (Background) + +2026-09-28 06:38 UTC,dev 后端因 `internal/docgov` 共享 map 并发读写 fatal 退出。Vite 代理在后端缺席期间对登录等全部 API 返回 500。本报告记录修复后的工程回归。产品 PI 全链路不适用:变更是治理引擎内存锁,不改对话或文档业务规则。 + +## 二、 测试目标 (Test Objectives) + +- 不同 docID 并发创建、读取、列审计/批注、写批注时,`-race` 不报告 data race,进程不 fatal。 +- 并发结束后每个 docID 仍能读回。 + +## 三、 测试方案与环境 (Test Plan & Environment) + +- OS:Linux;`go version go1.27.1 linux/amd64` +- 工作区:`.worktrees/docgov-shared-map-race-2026-09-28`,提交 `a90cce73` +- 层级:模块单元测试 + race detector。无数据库。产品 PI:NOT REQUIRED(非产品工作流变更;依据验证契约对纯工程锁修复的范围,以及本报告不把单测写成产品 PASS)。 +- 用例:`TestMemMapsNoRaceAcrossDocs` + +## 四、 预期结果 (Expected Results) + +`go test -race` 该用例 PASS,且 48 个文档均可 `GetDocument`。 + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +- 测试输入 (Input):`go test -race -count=1 -timeout 180s -v ./internal/docgov/ -run TestMemMapsNoRaceAcrossDocs` +- 实际输出 (Actual Output): + +``` +=== RUN TestMemMapsNoRaceAcrossDocs +--- PASS: TestMemMapsNoRaceAcrossDocs (0.00s) +PASS +ok chat-agent-system/internal/docgov 1.012s +``` + +- 评价判定 (Evaluation / Verdict):PASS +- 性能与耗时 (Duration & Latency):包测试约 1.012s;用例本身 0.00s(race 插桩耗时在包级) + +同一次会话里不带 `-run` 的 `go test -race -count=1 -timeout 180s ./internal/docgov/` 亦为 `ok`(1.012s)。dev 进程重建后的 `/health` 与登录复验在合入之后补记,本段不将其记为已完成。 + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +- `go test -race ./internal/docgov/`:PASS(含 `TestMemMapsNoRaceAcrossDocs`) +- 未跑产品 PI、未跑全仓 `go test` + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +工程回归达到本修复的单测终点。合入 local dev 后需重建 `bin/agent-server` 并确认 `/health` 与空登录请求返回 401 而不是 500。`ISSUE-20260926-docgov-memdocs-poison` 仍开放。