Files

78 lines
2.0 KiB
Go

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)
}