forked from coinbase/mesh-sdk-go
-
Notifications
You must be signed in to change notification settings - Fork 1
/
mutex_map.go
116 lines (103 loc) · 3.14 KB
/
mutex_map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright 2020 Coinbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"sync"
)
const (
unlockPriority = true
)
// MutexMap is a struct that allows for
// acquiring a *PriorityMutex via a string identifier
// or for acquiring a global mutex that blocks
// the acquisition of any identifier mutexes.
//
// This is useful for coordinating concurrent, non-overlapping
// writes in the storage package.
type MutexMap struct {
entries *ShardedMap
globalMutex sync.RWMutex
}
// mutexMapEntry is the primitive used
// to track claimed *PriorityMutex.
type mutexMapEntry struct {
lock *PriorityMutex
count int
}
// NewMutexMap returns a new *MutexMap.
func NewMutexMap(shards int) *MutexMap {
return &MutexMap{
entries: NewShardedMap(shards),
}
}
// GLock acquires an exclusive lock across
// an entire *MutexMap.
func (m *MutexMap) GLock() {
m.globalMutex.Lock()
}
// GUnlock releases an exclusive lock
// held for an entire *MutexMap.
func (m *MutexMap) GUnlock() {
m.globalMutex.Unlock()
}
// Lock acquires a lock for a particular identifier, as long
// as no other caller has the global mutex or a lock
// by the same identifier.
func (m *MutexMap) Lock(identifier string, priority bool) {
// We acquire a RLock on m.globalMutex before
// acquiring our identifier lock to ensure no
// goroutine holds an identifier mutex while
// the m.globalMutex is also held.
m.globalMutex.RLock()
// We acquire m when adding items to m.table
// so that we don't accidentally overwrite
// lock created by another goroutine.
data := m.entries.Lock(identifier, priority)
raw, ok := data[identifier]
var entry *mutexMapEntry
if !ok {
entry = &mutexMapEntry{
lock: new(PriorityMutex),
}
data[identifier] = entry
} else {
entry = raw.(*mutexMapEntry)
}
entry.count++
m.entries.Unlock(identifier)
// Once we have a m.globalMutex.RLock, it is
// safe to acquire an identifier lock.
entry.lock.Lock(priority)
}
// Unlock releases a lock held for a particular identifier.
func (m *MutexMap) Unlock(identifier string) {
// The lock at a particular identifier MUST
// exist by the time we unlock, otherwise
// it would not have been possible to get
// the lock to begin with.
data := m.entries.Lock(identifier, unlockPriority)
entry := data[identifier].(*mutexMapEntry)
if entry.count <= 1 { // this should never be < 0
delete(data, identifier)
} else {
entry.count--
entry.lock.Unlock()
}
m.entries.Unlock(identifier)
// We release the globalMutex after unlocking
// the identifier lock, otherwise it would be possible
// for GLock to be acquired while still holding some
// lock in the table.
m.globalMutex.RUnlock()
}