Files
forgejo/modules/cache/mutex_map.go
2025-11-16 08:53:27 -07:00

61 lines
1.5 KiB
Go

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