/* * Minio Cloud Storage, (C) 2016, 2017 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 ( "crypto/rand" "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 %s, %s, %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 %s, %s, %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 %s, %s, %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 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 { var lType lockType if readLock { lType = debugRLockStr } else { lType = debugWLockStr } return debugLockInfo{ lockSource: lockSource, lType: lType, status: status, since: UTCNow(), } } // 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 { const opsIDLen = 16 opsIDBytes := make([]byte, opsIDLen) if _, err := rand.Read(opsIDBytes); err != nil { panic(err) } for i := 0; i < opsIDLen; i++ { opsIDBytes[i] = alphaNumericTable[opsIDBytes[i]%alphaNumericTableLen] } return string(opsIDBytes) }