Defer locked-period supplements to a pending ledger, route duty-holder notifications, persist stage-scoped comments, and prefer Approver for reopen.
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package docgov
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
// docLockIndex 管理按 doc_id 的细粒度互斥锁。
|
||
// 全局锁只保护「分锁表」本身(短临界区);文档间操作互不阻塞。
|
||
// 锁序约定: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 对应的互斥锁指针;调用方必须在 meta 锁内无其他副作用。
|
||
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 绑定一个 docID 的已获取互斥锁,确保按序释放。
|
||
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() })
|
||
}
|
||
}
|
||
|
||
func (e *GovernanceEngine) acquireDocLock(docID string) docLockGuard {
|
||
mu := e.docLocks.lock(docID)
|
||
mu.Lock()
|
||
return docLockGuard{mu: mu}
|
||
}
|
||
|
||
// withDocLock 以 docID 粒度执行 fn;fn 内的 DB I/O 仍持该文档锁,
|
||
// 但不再阻塞其他文档(原实现为全局 RWMutex)。
|
||
func (e *GovernanceEngine) withDocLock(docID string, fn func() error) error {
|
||
g := e.acquireDocLock(docID)
|
||
defer g.Release()
|
||
return fn()
|
||
}
|
||
|
||
// lookupMemDoc 返回内存中的文档记录(需在 doc 锁内调用)。
|
||
func (e *GovernanceEngine) lookupMemDoc(docID string) (*DocumentRecord, bool) {
|
||
doc, ok := e.memDocs[docID]
|
||
return doc, ok
|
||
}
|
||
|
||
// ensureDocLockedWithDB 在 doc 锁内做内存查找 + DB 兜底加载。
|
||
// 对应原 ensureDocumentLocked,但锁粒度为单文档。
|
||
func (e *GovernanceEngine) ensureDocLockedWithDB(ctx context.Context, docID string) (*DocumentRecord, error) {
|
||
if doc, ok := e.memDocs[docID]; ok {
|
||
return doc, nil
|
||
}
|
||
if e.pool != nil {
|
||
doc, err := e.loadDocumentFromDB(ctx, docID)
|
||
if err == nil && doc != nil {
|
||
return doc, nil
|
||
}
|
||
}
|
||
return nil, ErrDocumentNotFound
|
||
}
|
||
|
||
// loadDocumentFromDB 在无全局锁状态下从 PG 装载文档记录到内存。
|
||
// 这是 T20 B3 的核心:DB RTT 不再进入全局临界区。
|
||
func (e *GovernanceEngine) loadDocumentFromDB(ctx context.Context, docID string) (*DocumentRecord, error) {
|
||
e.mu.Lock()
|
||
defer e.mu.Unlock()
|
||
return e.loadDocumentFromDBLocked(ctx, docID)
|
||
}
|