mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	This patch re-writes FS backend to support shared backend sharing locks for safe concurrent access across multiple servers.
		
			
				
	
	
		
			239 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * 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 (
 | 
						|
	"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 <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 {
 | 
						|
	var lType lockType
 | 
						|
	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 {
 | 
						|
	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)
 | 
						|
}
 |