Skip to content

Commit 5bebda8

Browse files
committed
multimutex: add hash mutex
1 parent f907fbc commit 5bebda8

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

multimutex/hash_mutex.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package multimutex
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
"github.com/lightningnetwork/lnd/lntypes"
8+
)
9+
10+
// HashMutex is a struct that keeps track of a set of mutexes with a given hash.
11+
// It can be used for making sure only one goroutine gets given the mutex per
12+
// hash.
13+
type HashMutex struct {
14+
// mutexes is a map of hashes to a cntMutex. The cntMutex for
15+
// a given hash will hold the mutex to be used by all
16+
// callers requesting access for the hash, in addition to
17+
// the count of callers.
18+
mutexes map[lntypes.Hash]*cntMutex
19+
20+
// mapMtx is used to give synchronize concurrent access
21+
// to the mutexes map.
22+
mapMtx sync.Mutex
23+
}
24+
25+
// NewHashMutex creates a new Mutex.
26+
func NewHashMutex() *HashMutex {
27+
return &HashMutex{
28+
mutexes: make(map[lntypes.Hash]*cntMutex),
29+
}
30+
}
31+
32+
// Lock locks the mutex by the given hash. If the mutex is already
33+
// locked by this hash, Lock blocks until the mutex is available.
34+
func (c *HashMutex) Lock(hash lntypes.Hash) {
35+
c.mapMtx.Lock()
36+
mtx, ok := c.mutexes[hash]
37+
if ok {
38+
// If the mutex already existed in the map, we
39+
// increment its counter, to indicate that there
40+
// now is one more goroutine waiting for it.
41+
mtx.cnt++
42+
} else {
43+
// If it was not in the map, it means no other
44+
// goroutine has locked the mutex for this hash,
45+
// and we can create a new mutex with count 1
46+
// and add it to the map.
47+
mtx = &cntMutex{
48+
cnt: 1,
49+
}
50+
c.mutexes[hash] = mtx
51+
}
52+
c.mapMtx.Unlock()
53+
54+
// Acquire the mutex for this hash.
55+
mtx.Lock()
56+
}
57+
58+
// Unlock unlocks the mutex by the given hash. It is a run-time
59+
// error if the mutex is not locked by the hash on entry to Unlock.
60+
func (c *HashMutex) Unlock(hash lntypes.Hash) {
61+
// Since we are done with all the work for this
62+
// update, we update the map to reflect that.
63+
c.mapMtx.Lock()
64+
65+
mtx, ok := c.mutexes[hash]
66+
if !ok {
67+
// The mutex not existing in the map means
68+
// an unlock for an hash not currently locked
69+
// was attempted.
70+
panic(fmt.Sprintf("double unlock for hash %v",
71+
hash))
72+
}
73+
74+
// Decrement the counter. If the count goes to
75+
// zero, it means this caller was the last one
76+
// to wait for the mutex, and we can delete it
77+
// from the map. We can do this safely since we
78+
// are under the mapMtx, meaning that all other
79+
// goroutines waiting for the mutex already
80+
// have incremented it, or will create a new
81+
// mutex when they get the mapMtx.
82+
mtx.cnt--
83+
if mtx.cnt == 0 {
84+
delete(c.mutexes, hash)
85+
}
86+
c.mapMtx.Unlock()
87+
88+
// Unlock the mutex for this hash.
89+
mtx.Unlock()
90+
}

0 commit comments

Comments
 (0)