lnd/multimutex/hash_mutex.go
2020-04-08 08:54:05 +02:00

91 lines
2.4 KiB
Go

package multimutex
import (
"fmt"
"sync"
"github.com/lightningnetwork/lnd/lntypes"
)
// HashMutex is a struct that keeps track of a set of mutexes with a given hash.
// It can be used for making sure only one goroutine gets given the mutex per
// hash.
type HashMutex struct {
// mutexes is a map of hashes to a cntMutex. The cntMutex for
// a given hash will hold the mutex to be used by all
// callers requesting access for the hash, in addition to
// the count of callers.
mutexes map[lntypes.Hash]*cntMutex
// mapMtx is used to give synchronize concurrent access
// to the mutexes map.
mapMtx sync.Mutex
}
// NewHashMutex creates a new Mutex.
func NewHashMutex() *HashMutex {
return &HashMutex{
mutexes: make(map[lntypes.Hash]*cntMutex),
}
}
// Lock locks the mutex by the given hash. If the mutex is already
// locked by this hash, Lock blocks until the mutex is available.
func (c *HashMutex) Lock(hash lntypes.Hash) {
c.mapMtx.Lock()
mtx, ok := c.mutexes[hash]
if ok {
// If the mutex already existed in the map, we
// increment its counter, to indicate that there
// now is one more goroutine waiting for it.
mtx.cnt++
} else {
// If it was not in the map, it means no other
// goroutine has locked the mutex for this hash,
// and we can create a new mutex with count 1
// and add it to the map.
mtx = &cntMutex{
cnt: 1,
}
c.mutexes[hash] = mtx
}
c.mapMtx.Unlock()
// Acquire the mutex for this hash.
mtx.Lock()
}
// Unlock unlocks the mutex by the given hash. It is a run-time
// error if the mutex is not locked by the hash on entry to Unlock.
func (c *HashMutex) Unlock(hash lntypes.Hash) {
// Since we are done with all the work for this
// update, we update the map to reflect that.
c.mapMtx.Lock()
mtx, ok := c.mutexes[hash]
if !ok {
// The mutex not existing in the map means
// an unlock for an hash not currently locked
// was attempted.
panic(fmt.Sprintf("double unlock for hash %v",
hash))
}
// Decrement the counter. If the count goes to
// zero, it means this caller was the last one
// to wait for the mutex, and we can delete it
// from the map. We can do this safely since we
// are under the mapMtx, meaning that all other
// goroutines waiting for the mutex already
// have incremented it, or will create a new
// mutex when they get the mapMtx.
mtx.cnt--
if mtx.cnt == 0 {
delete(c.mutexes, hash)
}
c.mapMtx.Unlock()
// Unlock the mutex for this hash.
mtx.Unlock()
}