// Copyright 2025 The Forgejo Authors. All rights reserved.// SPDX-License-Identifier: GPL-3.0-or-laterpackage cacheimport ( "sync")// MutexMap is basically a map[string]sync.Mutex which allows you to have one mutex per string key being locked. Unlike// a map[string]sync.Mutex, this map will automatically remove the Mutexes from itself when they are not being waited// for, preventing resource waste. It does this by keeping a reference count of the current Lock calls for the given// key.type MutexMap struct { mu sync.Mutex // mutex to be held when accessing mutexMap mutexMap map[string]*refcountMutex}type refcountMutex struct { refCount int // access to refCount is protected by the MutexMap's mu sync.Mutex}// Locks the given key, and returns a function that must be invoked to unlock the key.func (m *MutexMap) Lock(key string) func() { m.mu.Lock() if m.mutexMap == nil { m.mutexMap = make(map[string]*refcountMutex) } mutex, ok := m.mutexMap[key] if !ok { mutex = &refcountMutex{} m.mutexMap[key] = mutex } mutex.refCount++ m.mu.Unlock() mutex.Lock() unlockPending := true return func() { if !unlockPending { // unlocking twice would cause incorrect reference counts and might release another goroutine's mutex -- try // to detect and panic so that this programming error can be found closest to the source. panic("MutexMap unlock invoked twice") } unlockPending = false mutex.Unlock() m.mu.Lock() mutex.refCount-- if mutex.refCount == 0 { delete(m.mutexMap, key) } m.mu.Unlock() }}
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package cache
import (
"sync"
)
// MutexMap is basically a map[string]sync.Mutex which allows you to have one mutex per string key being locked. Unlike
// a map[string]sync.Mutex, this map will automatically remove the Mutexes from itself when they are not being waited
// for, preventing resource waste. It does this by keeping a reference count of the current Lock calls for the given
// key.
type MutexMap struct {
mu sync.Mutex // mutex to be held when accessing mutexMap
mutexMap map[string]*refcountMutex
}
type refcountMutex struct {
refCount int // access to refCount is protected by the MutexMap's mu
sync.Mutex
}
// Locks the given key, and returns a function that must be invoked to unlock the key.
func (m *MutexMap) Lock(key string) func() {
m.mu.Lock()
if m.mutexMap == nil {
m.mutexMap = make(map[string]*refcountMutex)
}
mutex, ok := m.mutexMap[key]
if !ok {
mutex = &refcountMutex{}
m.mutexMap[key] = mutex
}
mutex.refCount++
m.mu.Unlock()
mutex.Lock()
unlockPending := true
return func() {
if !unlockPending {
// unlocking twice would cause incorrect reference counts and might release another goroutine's mutex -- try
// to detect and panic so that this programming error can be found closest to the source.
panic("MutexMap unlock invoked twice")
}
unlockPending = false
mutex.Unlock()
m.mu.Lock()
mutex.refCount--
if mutex.refCount == 0 {
delete(m.mutexMap, key)
}
m.mu.Unlock()
}
}