package docrevision import ( "context" "sync" ) // docLockIndex 管理按 doc_id 的细粒度互斥锁(T20 B3)。 // 全局 meta 锁只保护「分锁表」本身;文档间 hydrate/commit 互不阻塞。 // 锁序:docLocks.meta → docID(如需多文档,按 docID 字典序)。 type docLockIndex struct { meta sync.Mutex locks map[string]*sync.Mutex } func newDocLockIndex() *docLockIndex { return &docLockIndex{locks: map[string]*sync.Mutex{}} } // lock 返回 docID 的互斥锁指针(仅查表/建表,短临界区)。 func (d *docLockIndex) lock(docID string) *sync.Mutex { d.meta.Lock() defer d.meta.Unlock() mu, ok := d.locks[docID] if !ok { mu = &sync.Mutex{} d.locks[docID] = mu } return mu } // docLockGuard 持有一个已 Lock 的 doc 级互斥锁;Release 幂等。 type docLockGuard struct { mu *sync.Mutex release sync.Once } func (g *docLockGuard) Release() { if g != nil && g.mu != nil { g.release.Do(func() { g.mu.Unlock() }) } } // lockDoc 获取 docID 的文档级写锁。 // 返回值持有已获取的互斥锁;defer g.Release() 释放。 func (s *Service) lockDoc(docID string) *docLockGuard { mu := s.docLocks.lock(docID) mu.Lock() return &docLockGuard{mu: mu} } // hydrateDoc 保证 docID 的账目在内存中可用(T20 B3 版本)。 // 两段式: // 1. 短临界区检查 roots(已 hydrate 直接返回); // 2. 持 doc 级锁执行既有 hydrateDocLocked(其内部 PG RTT 只阻塞同文档)。 // // 该函数自身不取 s.mu;s.mu 的获取由调用方在 doc 锁之后进行, // 维持锁序 docLocks[docID] → s.mu(全局内存表)→ docLocks.meta(查表瞬间)。 func (s *Service) hydrateDoc(ctx context.Context, docID string) error { if !s.usingPG() || docID == "" { return nil } s.mu.Lock() hydrated := false if root, ok := s.roots[docID]; ok && root.LedgerVersionID != nil { hydrated = true } s.mu.Unlock() if hydrated { return nil } g := s.lockDoc(docID) defer g.Release() s.mu.Lock() defer s.mu.Unlock() return s.hydrateDocLocked(ctx, docID) }