78 lines
2.0 KiB
Go
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)
|
|
}
|