github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/pkg/lock/keylock.go (about)

     1  // Copyright 2015 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lock
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"syscall"
    23  
    24  	"github.com/hashicorp/errwrap"
    25  )
    26  
    27  const (
    28  	defaultDirPerm     os.FileMode = 0660
    29  	defaultFilePerm    os.FileMode = 0660
    30  	defaultLockRetries             = 3
    31  )
    32  
    33  type keyLockMode uint
    34  
    35  const (
    36  	keyLockExclusive keyLockMode = 1 << iota
    37  	keyLockShared
    38  	keyLockNonBlocking
    39  )
    40  
    41  // KeyLock is a lock for a specific key. The lock file is created inside a
    42  // directory using the key name.
    43  // This is useful when multiple processes want to take a lock but cannot use
    44  // FileLock as they don't have a well defined file on the filesystem.
    45  // key value must be a valid file name (as the lock file is named after the key
    46  // value).
    47  type KeyLock struct {
    48  	lockDir string
    49  	key     string
    50  	// The lock on the key
    51  	keyLock *FileLock
    52  }
    53  
    54  // NewKeyLock returns a KeyLock for the specified key without acquisition.
    55  // lockdir is the directory where the lock file will be created. If lockdir
    56  // doesn't exists it will be created.
    57  // key value must be a valid file name (as the lock file is named after the key
    58  // value).
    59  func NewKeyLock(lockDir string, key string) (*KeyLock, error) {
    60  	err := os.MkdirAll(lockDir, defaultDirPerm)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	keyLockFile := filepath.Join(lockDir, key)
    65  	// create the file if it doesn't exists
    66  	f, err := os.OpenFile(keyLockFile, os.O_RDONLY|os.O_CREATE, defaultFilePerm)
    67  	if err != nil {
    68  		return nil, errwrap.Wrap(errors.New("error creating key lock file"), err)
    69  	}
    70  	f.Close()
    71  	keyLock, err := NewLock(keyLockFile, RegFile)
    72  	if err != nil {
    73  		return nil, errwrap.Wrap(errors.New("error opening key lock file"), err)
    74  	}
    75  	return &KeyLock{lockDir: lockDir, key: key, keyLock: keyLock}, nil
    76  }
    77  
    78  // Close closes the key lock which implicitly unlocks it as well
    79  func (l *KeyLock) Close() {
    80  	l.keyLock.Close()
    81  }
    82  
    83  // TryExclusiveLock takes an exclusive lock on a key without blocking.
    84  // This is idempotent when the KeyLock already represents an exclusive lock,
    85  // and tries promote a shared lock to exclusive atomically.
    86  // It will return ErrLocked if any lock is already held on the key.
    87  func (l *KeyLock) TryExclusiveKeyLock() error {
    88  	return l.lock(keyLockExclusive|keyLockNonBlocking, defaultLockRetries)
    89  }
    90  
    91  // TryExclusiveLock takes an exclusive lock on the key without blocking.
    92  // lockDir is the directory where the lock file will be created.
    93  // It will return ErrLocked if any lock is already held.
    94  func TryExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
    95  	return createAndLock(lockDir, key, keyLockExclusive|keyLockNonBlocking)
    96  }
    97  
    98  // ExclusiveLock takes an exclusive lock on a key.
    99  // This is idempotent when the KeyLock already represents an exclusive lock,
   100  // and promotes a shared lock to exclusive atomically.
   101  // It will block if an exclusive lock is already held on the key.
   102  func (l *KeyLock) ExclusiveKeyLock() error {
   103  	return l.lock(keyLockExclusive, defaultLockRetries)
   104  }
   105  
   106  // ExclusiveLock takes an exclusive lock on a key.
   107  // lockDir is the directory where the lock file will be created.
   108  // It will block if an exclusive lock is already held on the key.
   109  func ExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
   110  	return createAndLock(lockDir, key, keyLockExclusive)
   111  }
   112  
   113  // TrySharedLock takes a co-operative (shared) lock on the key without blocking.
   114  // This is idempotent when the KeyLock already represents a shared lock,
   115  // and tries demote an exclusive lock to shared atomically.
   116  // It will return ErrLocked if an exclusive lock already exists on the key.
   117  func (l *KeyLock) TrySharedKeyLock() error {
   118  	return l.lock(keyLockShared|keyLockNonBlocking, defaultLockRetries)
   119  }
   120  
   121  // TrySharedLock takes a co-operative (shared) lock on a key without blocking.
   122  // lockDir is the directory where the lock file will be created.
   123  // It will return ErrLocked if an exclusive lock already exists on the key.
   124  func TrySharedKeyLock(lockDir string, key string) (*KeyLock, error) {
   125  	return createAndLock(lockDir, key, keyLockShared|keyLockNonBlocking)
   126  }
   127  
   128  // SharedLock takes a co-operative (shared) lock on a key.
   129  // This is idempotent when the KeyLock already represents a shared lock,
   130  // and demotes an exclusive lock to shared atomically.
   131  // It will block if an exclusive lock is already held on the key.
   132  func (l *KeyLock) SharedKeyLock() error {
   133  	return l.lock(keyLockShared, defaultLockRetries)
   134  }
   135  
   136  // SharedLock takes a co-operative (shared) lock on a key.
   137  // lockDir is the directory where the lock file will be created.
   138  // It will block if an exclusive lock is already held on the key.
   139  func SharedKeyLock(lockDir string, key string) (*KeyLock, error) {
   140  	return createAndLock(lockDir, key, keyLockShared)
   141  }
   142  
   143  func createAndLock(lockDir string, key string, mode keyLockMode) (*KeyLock, error) {
   144  	keyLock, err := NewKeyLock(lockDir, key)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	err = keyLock.lock(mode, defaultLockRetries)
   149  	if err != nil {
   150  		keyLock.Close()
   151  		return nil, err
   152  	}
   153  	return keyLock, nil
   154  }
   155  
   156  // lock is the base function to take a lock and handle changed lock files
   157  // As there's the need to remove unused (see CleanKeyLocks) lock files without
   158  // races, a changed file detection is needed.
   159  //
   160  // Without changed file detection this can happen:
   161  //
   162  // Process A takes exclusive lock on file01
   163  // Process B waits for exclusive lock on file01.
   164  // Process A deletes file01 and then releases the lock.
   165  // Process B takes the lock on the removed file01 as it has the fd opened
   166  // Process C comes, creates the file as it doesn't exists, and it also takes an exclusive lock.
   167  // Now B and C thinks to own an exclusive lock.
   168  //
   169  // maxRetries can be passed, useful for testing.
   170  func (l *KeyLock) lock(mode keyLockMode, maxRetries int) error {
   171  	retries := 0
   172  	for {
   173  		var err error
   174  		var isExclusive bool
   175  		var isNonBlocking bool
   176  		if mode&keyLockExclusive != 0 {
   177  			isExclusive = true
   178  		}
   179  		if mode&keyLockNonBlocking != 0 {
   180  			isNonBlocking = true
   181  		}
   182  		switch {
   183  		case isExclusive && !isNonBlocking:
   184  			err = l.keyLock.ExclusiveLock()
   185  		case isExclusive && isNonBlocking:
   186  			err = l.keyLock.TryExclusiveLock()
   187  		case !isExclusive && !isNonBlocking:
   188  			err = l.keyLock.SharedLock()
   189  		case !isExclusive && isNonBlocking:
   190  			err = l.keyLock.TrySharedLock()
   191  		}
   192  		if err != nil {
   193  			return err
   194  		}
   195  
   196  		// Check that the file referenced by the lock fd is the same as
   197  		// the current file on the filesystem
   198  		var lockStat, curStat syscall.Stat_t
   199  		lfd, err := l.keyLock.Fd()
   200  		if err != nil {
   201  			return err
   202  		}
   203  		err = syscall.Fstat(lfd, &lockStat)
   204  		if err != nil {
   205  			return err
   206  		}
   207  		keyLockFile := filepath.Join(l.lockDir, l.key)
   208  		fd, err := syscall.Open(keyLockFile, syscall.O_RDONLY, 0)
   209  		// If there's an error opening the file return an error
   210  		if err != nil {
   211  			return err
   212  		}
   213  		if err := syscall.Fstat(fd, &curStat); err != nil {
   214  			syscall.Close(fd)
   215  			return err
   216  		}
   217  		syscall.Close(fd)
   218  		if lockStat.Ino == curStat.Ino && lockStat.Dev == curStat.Dev {
   219  			return nil
   220  		}
   221  		if retries >= maxRetries {
   222  			return fmt.Errorf("cannot acquire lock after %d retries", retries)
   223  		}
   224  
   225  		// If the file has changed discard this lock and try to take another lock.
   226  		l.keyLock.Close()
   227  		nl, err := NewKeyLock(l.lockDir, l.key)
   228  		if err != nil {
   229  			return err
   230  		}
   231  		l.keyLock = nl.keyLock
   232  
   233  		retries++
   234  	}
   235  }
   236  
   237  // Unlock unlocks the key lock.
   238  func (l *KeyLock) Unlock() error {
   239  	err := l.keyLock.Unlock()
   240  	if err != nil {
   241  		return err
   242  	}
   243  	return nil
   244  }
   245  
   246  // CleanKeyLocks remove lock files from the lockDir.
   247  // For every key it tries to take an Exclusive lock on it and skip it if it
   248  // fails with ErrLocked
   249  func CleanKeyLocks(lockDir string) error {
   250  	f, err := os.Open(lockDir)
   251  	if err != nil {
   252  		return errwrap.Wrap(errors.New("error opening lockDir"), err)
   253  	}
   254  	defer f.Close()
   255  	files, err := f.Readdir(0)
   256  	if err != nil {
   257  		return errwrap.Wrap(errors.New("error getting lock files list"), err)
   258  	}
   259  	for _, f := range files {
   260  		filename := filepath.Join(lockDir, f.Name())
   261  		keyLock, err := TryExclusiveKeyLock(lockDir, f.Name())
   262  		if err == ErrLocked {
   263  			continue
   264  		}
   265  		if err != nil {
   266  			return err
   267  		}
   268  
   269  		err = os.Remove(filename)
   270  		if err != nil {
   271  			keyLock.Close()
   272  			return errwrap.Wrap(errors.New("error removing lock file"), err)
   273  		}
   274  		keyLock.Close()
   275  	}
   276  	return nil
   277  }