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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
package mutexes
import (
"runtime"
"sync"
"sync/atomic"
)
// locktype defines maskable mutexmap lock types.
type locktype uint8
const (
// possible lock types.
lockTypeRead = locktype(1) << 0
lockTypeWrite = locktype(1) << 1
lockTypeMap = locktype(1) << 2
// possible mutexmap states.
stateUnlockd = uint8(0)
stateRLocked = uint8(1)
stateLocked = uint8(2)
stateInUse = uint8(3)
)
// permitLockType returns if provided locktype is permitted to go ahead in current state.
func permitLockType(state uint8, lt locktype) bool {
switch state {
// Unlocked state
// (all allowed)
case stateUnlockd:
return true
// Keys locked, no state lock.
// (don't allow map locks)
case stateInUse:
return lt&lockTypeMap == 0
// Read locked
// (only allow read locks)
case stateRLocked:
return lt&lockTypeRead != 0
// Write locked
// (none allowed)
case stateLocked:
return false
// shouldn't reach here
default:
panic("unexpected state")
}
}
// MutexMap is a structure that allows having a map of self-evicting mutexes
// by key. You do not need to worry about managing the contents of the map,
// only requesting RLock/Lock for keys, and ensuring to call the returned
// unlock functions.
type MutexMap struct {
mus map[string]RWMutex
mapMu sync.Mutex
pool sync.Pool
queue []func()
evict []func()
count int32
maxmu int32
state uint8
}
// NewMap returns a new MutexMap instance with provided max no. open mutexes.
func NewMap(max int32) MutexMap {
if max < 1 {
// Default = 128 * GOMAXPROCS
procs := runtime.GOMAXPROCS(0)
max = int32(procs * 128)
}
return MutexMap{
mus: make(map[string]RWMutex),
pool: sync.Pool{
New: func() interface{} {
return NewRW()
},
},
maxmu: max,
}
}
// acquire will either acquire a mutex from pool or alloc.
func (mm *MutexMap) acquire() RWMutex {
return mm.pool.Get().(RWMutex)
}
// release will release provided mutex to pool.
func (mm *MutexMap) release(mu RWMutex) {
mm.pool.Put(mu)
}
// spinLock will wait (using a mutex to sleep thread) until 'cond()' returns true,
// returning with map lock. Note that 'cond' is performed within a map lock.
func (mm *MutexMap) spinLock(cond func() bool) {
mu := mm.acquire()
defer mm.release(mu)
for {
// Get map lock
mm.mapMu.Lock()
// Check if return
if cond() {
return
}
// Queue ourselves
unlock := mu.Lock()
mm.queue = append(mm.queue, unlock)
mm.mapMu.Unlock()
// Wait on notify
mu.Lock()()
}
}
// lockMutex will acquire a lock on the mutex at provided key, handling earlier allocated mutex if provided. Unlocks map on return.
func (mm *MutexMap) lockMutex(key string, lt locktype) func() {
var unlock func()
// Incr counter
mm.count++
// Check for existing mutex at key
mu, ok := mm.mus[key]
if !ok {
// Alloc from pool
mu = mm.acquire()
mm.mus[key] = mu
// Queue mutex for eviction
mm.evict = append(mm.evict, func() {
delete(mm.mus, key)
mm.pool.Put(mu)
})
}
// If no state, set in use.
// State will already have been
// set if this is from LockState{}
if mm.state == stateUnlockd {
mm.state = stateInUse
}
switch {
// Read lock
case lt&lockTypeRead != 0:
unlock = mu.RLock()
// Write lock
case lt&lockTypeWrite != 0:
unlock = mu.Lock()
// shouldn't reach here
default:
panic("unexpected lock type")
}
// Unlock map + return
mm.mapMu.Unlock()
return func() {
mm.mapMu.Lock()
unlock()
go mm.onUnlock()
}
}
// onUnlock is performed as the final (async) stage of releasing an acquired key / map mutex.
func (mm *MutexMap) onUnlock() {
// Decr counter
mm.count--
if mm.count < 1 {
// Perform all queued evictions
for i := 0; i < len(mm.evict); i++ {
mm.evict[i]()
}
// Notify all waiting goroutines
for i := 0; i < len(mm.queue); i++ {
mm.queue[i]()
}
// Reset the map state
mm.evict = nil
mm.queue = nil
mm.state = stateUnlockd
}
// Finally, unlock
mm.mapMu.Unlock()
}
// RLockMap acquires a read lock over the entire map, returning a lock state for acquiring key read locks.
// Please note that the 'unlock()' function will block until all keys locked from this state are unlocked.
func (mm *MutexMap) RLockMap() *LockState {
return mm.getMapLock(lockTypeRead)
}
// LockMap acquires a write lock over the entire map, returning a lock state for acquiring key read/write locks.
// Please note that the 'unlock()' function will block until all keys locked from this state are unlocked.
func (mm *MutexMap) LockMap() *LockState {
return mm.getMapLock(lockTypeWrite)
}
// RLock acquires a mutex read lock for supplied key, returning an RUnlock function.
func (mm *MutexMap) RLock(key string) (runlock func()) {
return mm.getLock(key, lockTypeRead)
}
// Lock acquires a mutex write lock for supplied key, returning an Unlock function.
func (mm *MutexMap) Lock(key string) (unlock func()) {
return mm.getLock(key, lockTypeWrite)
}
// getLock will fetch lock of provided type, for given key, returning unlock function.
func (mm *MutexMap) getLock(key string, lt locktype) func() {
// Spin until achieve lock
mm.spinLock(func() bool {
return permitLockType(mm.state, lt) &&
mm.count < mm.maxmu // not overloaded
})
// Perform actual mutex lock
return mm.lockMutex(key, lt)
}
// getMapLock will acquire a map lock of provided type, returning a LockState session.
func (mm *MutexMap) getMapLock(lt locktype) *LockState {
// Spin until achieve lock
mm.spinLock(func() bool {
return permitLockType(mm.state, lt|lockTypeMap) &&
mm.count < mm.maxmu // not overloaded
})
// Incr counter
mm.count++
switch {
// Set read lock state
case lt&lockTypeRead != 0:
mm.state = stateRLocked
// Set write lock state
case lt&lockTypeWrite != 0:
mm.state = stateLocked
default:
panic("unexpected lock type")
}
// Unlock + return
mm.mapMu.Unlock()
return &LockState{
mmap: mm,
ltyp: lt,
}
}
// LockState represents a window to a locked MutexMap.
type LockState struct {
wait sync.WaitGroup
mmap *MutexMap
done uint32
ltyp locktype
}
// Lock: see MutexMap.Lock() definition. Will panic if map only read locked.
func (st *LockState) Lock(key string) (unlock func()) {
return st.getLock(key, lockTypeWrite)
}
// RLock: see MutexMap.RLock() definition.
func (st *LockState) RLock(key string) (runlock func()) {
return st.getLock(key, lockTypeRead)
}
// UnlockMap will close this state and release the currently locked map.
func (st *LockState) UnlockMap() {
// Set state to finished (or panic if already done)
if !atomic.CompareAndSwapUint32(&st.done, 0, 1) {
panic("called UnlockMap() on expired state")
}
// Wait until done
st.wait.Wait()
// Async reset map
st.mmap.mapMu.Lock()
go st.mmap.onUnlock()
}
// getLock: see MutexMap.getLock() definition.
func (st *LockState) getLock(key string, lt locktype) func() {
st.wait.Add(1) // track lock
// Check if closed, or if write lock is allowed
if atomic.LoadUint32(&st.done) == 1 {
panic("map lock closed")
} else if lt&lockTypeWrite != 0 &&
st.ltyp&lockTypeWrite == 0 {
panic("called .Lock() on rlocked map")
}
// Spin until achieve map lock
st.mmap.spinLock(func() bool {
return st.mmap.count < st.mmap.maxmu
}) // i.e. not overloaded
// Perform actual mutex lock
unlock := st.mmap.lockMutex(key, lt)
return func() {
unlock()
st.wait.Done()
}
}
|