Files
Agent-System/.doc/ops/short-term/2026-09-25-t20-s3-lock-split.md
T

4.8 KiB
Raw Blame History

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
internal/docrevision
internal/docgov
.doc/ops/short-term/2026-09-25-t20-s3-lock-split.md
.doc/test-reports/2026-09-25-t20-s3-lock-split-test-report.md
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-25 P4 批次;设计 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(与 docgov docLocks[docID] → e.mu 对称);docLocks.meta 仅在查表瞬间持有。

关键设计决策

  1. hydrate 保持在 doc 锁内(而非完全锁外):lockOpenVersionTX 的 FOR UPDATE 行锁 + 乐观 head/etag 校验依赖「检查-写入」原子性;锁外 hydrate + 短临界区合并需要引入版本 CAS,超出阶段 A「行为不变」边界(设计文档亦将完整缓存化列为阶段 B)。
  2. sync.Once 幂等 guard:防止 ApproverApprove 这类显式释放/重取模式下的双重 Unlock panic(首轮 race 测试捕获后修复)。
  3. 提案命名空间锁:提案不属于单一文档的互斥域(决策跨 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 痛点。