4.8 KiB
4.8 KiB
title, task_id, status, created, updated, owner, task_type, policy_revision, branch, worktree, base_dev_sha, write_scope, mutex, catalog, review_state, integration_state, deployment, cleanup, remote_sync
| title | task_id | status | created | updated | owner | task_type | policy_revision | branch | worktree | base_dev_sha | write_scope | mutex | catalog | review_state | integration_state | deployment | cleanup | remote_sync | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| T20 S3 锁批:B3 docgov/docrevision 分锁 | t20-s3-lock-split-2026-09-25 | in_progress | 2026-09-25 | 2026-09-25 | Cursor | hardening | AGENTS.md @ d93c1ab3 |
feature/t20-s3-lock-split-2026-09-25 | .worktrees/t20-s3-lock-split-2026-09-25 | d93c1ab3d6 |
|
docgov/docrevision | R-t20-s3-lock-split(overlap 探测无硬/软重叠) | done | pending | not_required(纯进程内并发改造,无部署面变化) | pending | not_requested |
T20 S3 锁批:B3 docgov/docrevision 分锁
用户目标与授权
- 来源:
planned-tasks-closeout-plan-2026-09-25P4 批次;设计 T20 B3(阶段 A)。 - 目标:单文档慢 DB 查询不再阻塞全部文档的修订/租约路径。
实现方式(阶段 A,行为不变)
docgov(internal/docgov)
- 新增
lock_split.go:docLockIndex(map[docID]*sync.Mutex+ meta 锁只保护分锁表)与幂等docLockGuard。 - 迁移写路径为
acquireDocLock(docID):AcquireLease/ReleaseLease/HeartbeatLease/SaveDocumentContent/AssertLeaseFencing/CheckHumanCanEdit/AssertWritableForComment/AddComment/CreateOrGetDocument/LockDocumentDuty/MarkDocumentContentUpdated/SetDocumentWorkDir/RecordDocumentViewed/RecordDocumentEdited/CheckModificationPermission/SubmitForReview/ReviewApprove/ReviewReject/ApproverApprove/ApproverReject/ReopenForRevision/GetDocument/ListAuditLogs/ListComments/ProposeModification。 - 提案决策类(
DecideModificationProposal/MarkProposalApplied)以*proposal:<id>命名空间分锁。 ApproverApprove的 hook-out-of-lock 段改用 guard 释放/重取(替代原e.mu.Unlock()/Lock())。- 全局
e.mu保留:loadDocumentFromDBLocked内部短临界区写memDocs、hooks 注册、materialize模板段与hasCanonicalSibling内存兜底——均为纯内存操作,无 DB RTT。
docrevision(internal/docrevision)
- 新增
lock_split.go:同构docLockIndex+Service.lockDoc。 - 全部公开入口改为 doc 锁 → 全局
s.mu两段式(GetRevisionState/ListVersions/GetVersionContent/ManualOverlay/GetCandidate/GetChangeReport/CommitHumanRevision/OpenTemplateVersion/EnsureBootstrap/CreateAgentCandidate/CommitAgentVersion/ImportFreeformLegacy/SetDocWorkDir/SetLifecycle/EnsureRoot/ReconcileDocument/CommitMigrationRepair/InspectProjection/HeadContent/projectOutputMarkdown/ContentProjectionPending/MarkContentProjected)。 - hydrate 的 PG 查询仍在 doc 锁内(保证同文档 TOCTOU 语义),但不再进入跨文档临界路径——这正是 B3「单文档慢查询阻塞全部文档」的修复点。
- 锁序统一:
docLocks[docID] → s.mu(与 docgovdocLocks[docID] → e.mu对称);docLocks.meta仅在查表瞬间持有。
关键设计决策
- hydrate 保持在 doc 锁内(而非完全锁外):
lockOpenVersionTX的FOR UPDATE行锁 + 乐观 head/etag 校验依赖「检查-写入」原子性;锁外 hydrate + 短临界区合并需要引入版本 CAS,超出阶段 A「行为不变」边界(设计文档亦将完整缓存化列为阶段 B)。 sync.Once幂等 guard:防止ApproverApprove这类显式释放/重取模式下的双重 Unlock panic(首轮 race 测试捕获后修复)。- 提案命名空间锁:提案不属于单一文档的互斥域(决策跨 doc + proposal 表),独立命名空间避免与 doc 锁混序。
验证
go test -race ./internal/...:67 包全绿(含 docgov/docrevision/app 下游)。- 新增
lock_split_test.go三用例:TestParallelHydrateDoesNotSerializeAcrossDocs:docA 持锁期间 docB 可完成锁定(验收断言);TestParallelSameDocStillSerialized:同文档串行不放松(回归保护);TestConcurrentDistinctDocsCommits:4 文档×10 版并行提交链无交叉污染。
- 既有并发负例(
TestConcurrentHumanRevisions、document_commit_concurrency_pg_test所在包)随 67 包回归通过。
未做(明确边界)
- 阶段 B(PG 为 SSOT、内存缓存化、锁外 hydrate + 版本 CAS):设计文档标注「需单独容量评估」,不与本批绑定。
SaveDocumentContent的e.mu.Lock()中段保留为全局锁(原代码即两段式:AcquireLease(已分锁)→ 内容写段);该段无 DB hydrate,仅 persist 一条 UPDATE,非 B3 痛点。