forked from minio/minio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlock-instrument.go
229 lines (199 loc) · 7.32 KB
/
lock-instrument.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
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
/*
* Minio Cloud Storage, (C) 2016 Minio, 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 cmd
import (
"fmt"
"time"
)
type statusType string
const (
runningStatus statusType = "Running"
blockedStatus statusType = "Blocked"
)
type lockType string
const (
debugRLockStr lockType = "RLock"
debugWLockStr lockType = "WLock"
)
// debugLockInfo - represents a single lock's information, uniquely identified by opsID.
// See debugLockInfoPerVolumePath for more context.
type debugLockInfo struct {
// "RLock" or "WLock".
lType lockType
// Contains the backtrace of incl. the function which called (r)(un)lock.
lockSource string
// Status can be running/blocked.
status statusType
// Time of last status update.
since time.Time
}
// debugLockInfoPerVolumePath - lock state information on all locks held on (volume, path).
type debugLockInfoPerVolumePath struct {
counters *lockStat // Holds stats of lock held on (volume, path)
lockInfo map[string]debugLockInfo // Lock information per operation ID.
}
// LockInfoOriginMismatch - represents error when lock origin don't match.
type LockInfoOriginMismatch struct {
volume string
path string
opsID string
lockSource string
}
func (l LockInfoOriginMismatch) Error() string {
return fmt.Sprintf("No lock state stored for the lock originated at \"%s\", for <volume> %s, <path> %s, <opsID> %s",
l.lockSource, l.volume, l.path, l.opsID)
}
// LockInfoVolPathMissing - represents error when lock information is missing for a given (volume, path).
type LockInfoVolPathMissing struct {
volume string
path string
}
func (l LockInfoVolPathMissing) Error() string {
return fmt.Sprintf("No entry in debug Lock Map for Volume: %s, path: %s", l.volume, l.path)
}
// LockInfoOpsIDNotFound - represents error when lock info entry for a given operation ID doesn't exist.
type LockInfoOpsIDNotFound struct {
volume string
path string
opsID string
}
func (l LockInfoOpsIDNotFound) Error() string {
return fmt.Sprintf("No entry in lock info for <Operation ID> %s, <volume> %s, <path> %s", l.opsID, l.volume, l.path)
}
// LockInfoStateNotBlocked - represents error when lock info isn't in blocked state when it should be.
type LockInfoStateNotBlocked struct {
volume string
path string
opsID string
}
func (l LockInfoStateNotBlocked) Error() string {
return fmt.Sprintf("Lock state should be \"Blocked\" for <volume> %s, <path> %s, <opsID> %s", l.volume, l.path, l.opsID)
}
// Initialize lock info for given (volume, path).
func (n *nsLockMap) initLockInfoForVolumePath(param nsParam) {
n.debugLockMap[param] = &debugLockInfoPerVolumePath{
lockInfo: make(map[string]debugLockInfo),
counters: &lockStat{},
}
}
// Change the state of the lock from Blocked to Running.
func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockSource, opsID string, readLock bool) error {
// This function is called outside nsLockMap.mutex.Lock(), so must be held explicitly.
n.lockMapMutex.Lock()
defer n.lockMapMutex.Unlock()
// Check whether the lock info entry for <volume, path> pair already exists.
_, ok := n.debugLockMap[param]
if !ok {
return traceError(LockInfoVolPathMissing{param.volume, param.path})
}
// Check whether lock info entry for the given `opsID` exists.
lockInfo, ok := n.debugLockMap[param].lockInfo[opsID]
if !ok {
return traceError(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
}
// Check whether lockSource is same.
if lockInfo.lockSource != lockSource {
return traceError(LockInfoOriginMismatch{param.volume, param.path, opsID, lockSource})
}
// Status of the lock should be set to "Blocked".
if lockInfo.status != blockedStatus {
return traceError(LockInfoStateNotBlocked{param.volume, param.path, opsID})
}
// Change lock status to running and update the time.
n.debugLockMap[param].lockInfo[opsID] = newDebugLockInfo(lockSource, runningStatus, readLock)
// Update global lock stats.
n.counters.lockGranted()
// Update (volume, pair) lock stats.
n.debugLockMap[param].counters.lockGranted()
return nil
}
// newDebugLockInfo - Constructs a debugLockInfo value given lock source, status and type.
func newDebugLockInfo(lockSource string, status statusType, readLock bool) debugLockInfo {
lType := debugRLockStr
if readLock {
lType = debugRLockStr
} else {
lType = debugWLockStr
}
return debugLockInfo{
lockSource: lockSource,
lType: lType,
status: status,
since: time.Now().UTC(),
}
}
// Change the state of the lock to Blocked.
func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockSource, opsID string, readLock bool) error {
_, ok := n.debugLockMap[param]
if !ok {
// Lock info entry for (volume, pair) doesn't exist, initialize it.
n.initLockInfoForVolumePath(param)
}
// Mark lock status blocked for given opsID.
n.debugLockMap[param].lockInfo[opsID] = newDebugLockInfo(lockSource, blockedStatus, readLock)
// Update global lock stats.
n.counters.lockWaiting()
// Update (volume, path) lock stats.
n.debugLockMap[param].counters.lockWaiting()
return nil
}
// deleteLockInfoEntry - Deletes the lock information for given (volume, path).
// Called when nsLk.ref count is 0.
func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
// delete the lock info for the given operation.
if _, found := n.debugLockMap[param]; !found {
return traceError(LockInfoVolPathMissing{param.volume, param.path})
}
// The following stats update is relevant only in case of a
// ForceUnlock. In case of the last unlock on a (volume,
// path), this would be a no-op.
volumePathLocks := n.debugLockMap[param]
for _, lockInfo := range volumePathLocks.lockInfo {
granted := lockInfo.status == runningStatus
// Update global and (volume, path) stats.
n.counters.lockRemoved(granted)
volumePathLocks.counters.lockRemoved(granted)
}
delete(n.debugLockMap, param)
return nil
}
// deleteLockInfoEntry - Deletes lock info entry for given opsID.
// Called when the nsLk ref count for the given (volume, path) is
// not 0.
func (n *nsLockMap) deleteLockInfoEntryForOps(param nsParam, opsID string) error {
// delete the lock info for the given operation.
infoMap, found := n.debugLockMap[param]
if !found {
return traceError(LockInfoVolPathMissing{param.volume, param.path})
}
// The operation finished holding the lock on the resource, remove
// the entry for the given operation with the operation ID.
opsIDLock, foundInfo := infoMap.lockInfo[opsID]
if !foundInfo {
// Unlock request with invalid operation ID not accepted.
return traceError(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
}
// Update global and (volume, path) lock status.
granted := opsIDLock.status == runningStatus
n.counters.lockRemoved(granted)
infoMap.counters.lockRemoved(granted)
delete(infoMap.lockInfo, opsID)
return nil
}
// Return randomly generated string ID
func getOpsID() string {
return mustGetRequestID()
}