mirror of
https://github.com/clearlinux/rkt.git
synced 2026-08-22 23:37:00 +00:00
9685f37575
Inspired by a similar change in the Kubernetes project [1], this changes the copyright header to the more generic "The rkt Authors" rather than "CoreOS, Inc.", which is only sometimes correct. https://github.com/GoogleCloudPlatform/kubernetes/commit/6b3a6e6b983f967c88d14d26542ec6e30c49ebd3
275 lines
8.5 KiB
Go
275 lines
8.5 KiB
Go
// Copyright 2015 The rkt Authors
|
|
//
|
|
// 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 lock
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
const (
|
|
defaultDirPerm os.FileMode = 0660
|
|
defaultFilePerm os.FileMode = 0660
|
|
defaultLockRetries = 3
|
|
)
|
|
|
|
type keyLockMode uint
|
|
|
|
const (
|
|
keyLockExclusive keyLockMode = 1 << iota
|
|
keyLockShared
|
|
keyLockNonBlocking
|
|
)
|
|
|
|
// KeyLock is a lock for a specific key. The lock file is created inside a
|
|
// directory using the key name.
|
|
// This is useful when multiple processes want to take a lock but cannot use
|
|
// FileLock as they don't have a well defined file on the filesystem.
|
|
// key value must be a valid file name (as the lock file is named after the key
|
|
// value).
|
|
type KeyLock struct {
|
|
lockDir string
|
|
key string
|
|
// The lock on the key
|
|
keyLock *FileLock
|
|
}
|
|
|
|
// NewKeyLock returns a KeyLock for the specified key without acquisition.
|
|
// lockdir is the directory where the lock file will be created. If lockdir
|
|
// doesn't exists it will be created.
|
|
// key value must be a valid file name (as the lock file is named after the key
|
|
// value).
|
|
func NewKeyLock(lockDir string, key string) (*KeyLock, error) {
|
|
err := os.MkdirAll(lockDir, defaultDirPerm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyLockFile := filepath.Join(lockDir, key)
|
|
// create the file if it doesn't exists
|
|
f, err := os.OpenFile(keyLockFile, os.O_RDONLY|os.O_CREATE, defaultFilePerm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating key lock file: %v", err)
|
|
}
|
|
f.Close()
|
|
keyLock, err := NewLock(keyLockFile, RegFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error opening key lock file: %v", err)
|
|
}
|
|
return &KeyLock{lockDir: lockDir, key: key, keyLock: keyLock}, nil
|
|
}
|
|
|
|
// Close closes the key lock which implicitly unlocks it as well
|
|
func (l *KeyLock) Close() {
|
|
l.keyLock.Close()
|
|
}
|
|
|
|
// TryExclusiveLock takes an exclusive lock on a key without blocking.
|
|
// This is idempotent when the KeyLock already represents an exclusive lock,
|
|
// and tries promote a shared lock to exclusive atomically.
|
|
// It will return ErrLocked if any lock is already held on the key.
|
|
func (l *KeyLock) TryExclusiveKeyLock() error {
|
|
return l.lock(keyLockExclusive|keyLockNonBlocking, defaultLockRetries)
|
|
}
|
|
|
|
// TryExclusiveLock takes an exclusive lock on the key without blocking.
|
|
// lockDir is the directory where the lock file will be created.
|
|
// It will return ErrLocked if any lock is already held.
|
|
func TryExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
|
|
return createAndLock(lockDir, key, keyLockExclusive|keyLockNonBlocking)
|
|
}
|
|
|
|
// ExclusiveLock takes an exclusive lock on a key.
|
|
// This is idempotent when the KeyLock already represents an exclusive lock,
|
|
// and promotes a shared lock to exclusive atomically.
|
|
// It will block if an exclusive lock is already held on the key.
|
|
func (l *KeyLock) ExclusiveKeyLock() error {
|
|
return l.lock(keyLockExclusive, defaultLockRetries)
|
|
}
|
|
|
|
// ExclusiveLock takes an exclusive lock on a key.
|
|
// lockDir is the directory where the lock file will be created.
|
|
// It will block if an exclusive lock is already held on the key.
|
|
func ExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
|
|
return createAndLock(lockDir, key, keyLockExclusive)
|
|
}
|
|
|
|
// TrySharedLock takes a co-operative (shared) lock on the key without blocking.
|
|
// This is idempotent when the KeyLock already represents a shared lock,
|
|
// and tries demote an exclusive lock to shared atomically.
|
|
// It will return ErrLocked if an exclusive lock already exists on the key.
|
|
func (l *KeyLock) TrySharedKeyLock() error {
|
|
return l.lock(keyLockShared|keyLockNonBlocking, defaultLockRetries)
|
|
}
|
|
|
|
// TrySharedLock takes a co-operative (shared) lock on a key without blocking.
|
|
// lockDir is the directory where the lock file will be created.
|
|
// It will return ErrLocked if an exclusive lock already exists on the key.
|
|
func TrySharedKeyLock(lockDir string, key string) (*KeyLock, error) {
|
|
return createAndLock(lockDir, key, keyLockShared|keyLockNonBlocking)
|
|
}
|
|
|
|
// SharedLock takes a co-operative (shared) lock on a key.
|
|
// This is idempotent when the KeyLock already represents a shared lock,
|
|
// and demotes an exclusive lock to shared atomically.
|
|
// It will block if an exclusive lock is already held on the key.
|
|
func (l *KeyLock) SharedKeyLock() error {
|
|
return l.lock(keyLockShared, defaultLockRetries)
|
|
}
|
|
|
|
// SharedLock takes a co-operative (shared) lock on a key.
|
|
// lockDir is the directory where the lock file will be created.
|
|
// It will block if an exclusive lock is already held on the key.
|
|
func SharedKeyLock(lockDir string, key string) (*KeyLock, error) {
|
|
return createAndLock(lockDir, key, keyLockShared)
|
|
}
|
|
|
|
func createAndLock(lockDir string, key string, mode keyLockMode) (*KeyLock, error) {
|
|
keyLock, err := NewKeyLock(lockDir, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = keyLock.lock(mode, defaultLockRetries)
|
|
if err != nil {
|
|
keyLock.Close()
|
|
return nil, err
|
|
}
|
|
return keyLock, nil
|
|
}
|
|
|
|
// lock is the base function to take a lock and handle changed lock files
|
|
// As there's the need to remove unused (see CleanKeyLocks) lock files without
|
|
// races, a changed file detection is needed.
|
|
//
|
|
// Without changed file detection this can happen:
|
|
//
|
|
// Process A takes exclusive lock on file01
|
|
// Process B waits for exclusive lock on file01.
|
|
// Process A deletes file01 and then releases the lock.
|
|
// Process B takes the lock on the removed file01 as it has the fd opened
|
|
// Process C comes, creates the file as it doesn't exists, and it also takes an exclusive lock.
|
|
// Now B and C thinks to own an exclusive lock.
|
|
//
|
|
// maxRetries can be passed, useful for testing.
|
|
func (l *KeyLock) lock(mode keyLockMode, maxRetries int) error {
|
|
retries := 0
|
|
for {
|
|
var err error
|
|
var isExclusive bool
|
|
var isNonBlocking bool
|
|
if mode&keyLockExclusive != 0 {
|
|
isExclusive = true
|
|
}
|
|
if mode&keyLockNonBlocking != 0 {
|
|
isNonBlocking = true
|
|
}
|
|
switch {
|
|
case isExclusive && !isNonBlocking:
|
|
err = l.keyLock.ExclusiveLock()
|
|
case isExclusive && isNonBlocking:
|
|
err = l.keyLock.TryExclusiveLock()
|
|
case !isExclusive && !isNonBlocking:
|
|
err = l.keyLock.SharedLock()
|
|
case !isExclusive && isNonBlocking:
|
|
err = l.keyLock.TrySharedLock()
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check that the file referenced by the lock fd is the same as
|
|
// the current file on the filesystem
|
|
var lockStat, curStat syscall.Stat_t
|
|
lfd, err := l.keyLock.Fd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = syscall.Fstat(lfd, &lockStat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyLockFile := filepath.Join(l.lockDir, l.key)
|
|
fd, err := syscall.Open(keyLockFile, syscall.O_RDONLY, 0)
|
|
// If there's an error opening the file return an error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := syscall.Fstat(fd, &curStat); err != nil {
|
|
syscall.Close(fd)
|
|
return err
|
|
}
|
|
syscall.Close(fd)
|
|
if lockStat.Ino == curStat.Ino && lockStat.Dev == curStat.Dev {
|
|
return nil
|
|
}
|
|
if retries >= maxRetries {
|
|
return fmt.Errorf("cannot acquire lock after %d retries", retries)
|
|
}
|
|
|
|
// If the file has changed discard this lock and try to take another lock.
|
|
l.keyLock.Close()
|
|
nl, err := NewKeyLock(l.lockDir, l.key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l.keyLock = nl.keyLock
|
|
|
|
retries++
|
|
}
|
|
}
|
|
|
|
// Unlock unlocks the key lock.
|
|
func (l *KeyLock) Unlock() error {
|
|
err := l.keyLock.Unlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CleanKeyLocks remove lock files from the lockDir.
|
|
// For every key it tries to take an Exclusive lock on it and skip it if it
|
|
// fails with ErrLocked
|
|
func CleanKeyLocks(lockDir string) error {
|
|
f, err := os.Open(lockDir)
|
|
if err != nil {
|
|
return fmt.Errorf("error opening lockDir: %v", err)
|
|
}
|
|
defer f.Close()
|
|
files, err := f.Readdir(0)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting lock files list: %v", err)
|
|
}
|
|
for _, f := range files {
|
|
filename := filepath.Join(lockDir, f.Name())
|
|
keyLock, err := TryExclusiveKeyLock(lockDir, f.Name())
|
|
if err == ErrLocked {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.Remove(filename)
|
|
if err != nil {
|
|
keyLock.Close()
|
|
return fmt.Errorf("error removing lock file: %v", err)
|
|
}
|
|
keyLock.Close()
|
|
}
|
|
return nil
|
|
}
|