-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlock.go
171 lines (136 loc) · 3.13 KB
/
lock.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
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
package lockunique
import (
"errors"
"fmt"
"sync"
)
const maxArraySize = 50
var ErrNotLocked = errors.New("id not locked")
type lockID[T comparable] struct {
id T
queue int32
unlocked chan struct{}
}
type LockUnique[T comparable] struct {
lockArr []*lockID[T]
lockMap map[T]*lockID[T]
mu sync.Mutex
valid bool
useMap bool
nextFree int
}
// NewLockUnique creates a new LockUnique[T]. Using lock := &lockunique.LockUnique{} can be use as well.
func NewLockUnique[T comparable]() *LockUnique[T] {
return &LockUnique[T]{
lockArr: make([]*lockID[T], 0, maxArraySize),
valid: true,
}
}
// Lock acquires a lock for the given id.
func (l *LockUnique[T]) Lock(id T) {
l.mu.Lock()
if !l.valid {
l.lockArr = make([]*lockID[T], maxArraySize)
l.valid = true
}
var lock *lockID[T]
if l.useMap {
lock = l.lockMap[id]
} else {
for i := 0; i < l.nextFree; i++ {
if l.lockArr[i].id == id {
lock = l.lockArr[i]
break
}
}
}
if lock != nil {
// id is locked already
lock.queue++
l.mu.Unlock()
// wait for the current lock to be deleted
<-lock.unlocked
return
}
// if we are there, there is no current lock for this id
lock = &lockID[T]{
id: id,
unlocked: make(chan struct{}, 1), // cap of 1 so the unlocker never has to wait
}
if l.useMap {
l.lockMap[id] = lock
} else {
// using an array
if l.nextFree >= len(l.lockArr) {
if l.nextFree < maxArraySize {
l.lockArr = append(l.lockArr, lock)
} else {
// switch to using a map
l.useMap = true
l.lockMap = make(map[T]*lockID[T])
for _, lptr := range l.lockArr {
l.lockMap[lptr.id] = lptr
}
l.lockArr = l.lockArr[:0]
l.lockMap[id] = lock
l.nextFree = 0
}
} else {
l.lockArr[l.nextFree] = lock
}
l.nextFree++
}
// take first place in the queue
lock.queue = 1
l.mu.Unlock()
}
// Unlock releases the lock for the given id. If the id is not currently locked then an lockunique.ErrNotLocked is returned.
func (l *LockUnique[T]) Unlock(id T) error {
l.mu.Lock()
defer l.mu.Unlock()
if l.useMap {
lock, found := l.lockMap[id]
if !found {
return fmt.Errorf("%w: id = %v", ErrNotLocked, id)
}
// remove this lock from the queue
lock.queue--
if lock.queue == 0 {
delete(l.lockMap, id)
if len(l.lockMap) < maxArraySize / 2 {
// revert to array
for _, lptr := range l.lockMap {
l.lockArr = append(l.lockArr, lptr)
}
l.useMap = false
l.nextFree = len(l.lockArr)
l.lockMap = nil
}
return nil
}
// the next lock in the queue can proceed
lock.unlocked <- struct{}{}
return nil
}
// using an array
for i := 0; i < l.nextFree; i++ {
if l.lockArr[i].id == id {
// remove this lock from the queue
l.lockArr[i].queue--
if l.lockArr[i].queue == 0 {
// no other locks waiting for this ID, remove from locks
if i == l.nextFree-1 {
l.lockArr[i] = nil
} else {
l.lockArr[i] = l.lockArr[l.nextFree-1]
}
l.nextFree--
return nil
}
// the next lock in the queue can proceed
l.lockArr[i].unlocked <- struct{}{}
return nil
}
}
return fmt.Errorf("%w: id = %v", ErrNotLocked, id)
}