github.com/zhyoulun/cilium@v1.6.12/pkg/kvstore/lock.go (about)

     1  // Copyright 2016-2019 Authors of Cilium
     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 kvstore
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"time"
    21  
    22  	"github.com/cilium/cilium/pkg/debug"
    23  	"github.com/cilium/cilium/pkg/defaults"
    24  	"github.com/cilium/cilium/pkg/lock"
    25  	uuidfactor "github.com/cilium/cilium/pkg/uuid"
    26  
    27  	"github.com/davecgh/go-spew/spew"
    28  	"github.com/pborman/uuid"
    29  	"github.com/sirupsen/logrus"
    30  )
    31  
    32  var (
    33  	kvstoreLocks = pathLocks{lockPaths: map[string]lockOwner{}}
    34  
    35  	// staleLockTimeout is the timeout after which waiting for a believed
    36  	// other local lock user for the same key is given up on and etcd is
    37  	// asked directly. It is still highly unlikely that concurrent access
    38  	// occurs as only one consumer will manage to acquire the newly
    39  	// released lock. The only possibility of concurrent access is if a
    40  	// consumer is *still* holding the lock but this is highly unlikely
    41  	// given the duration of this timeout.
    42  	staleLockTimeout = defaults.KVStoreStaleLockTimeout
    43  )
    44  
    45  type KVLocker interface {
    46  	Unlock() error
    47  	// Comparator returns an object that should be used by the KVStore to make
    48  	// sure if the lock is still valid for its client or nil if no such
    49  	// verification exists.
    50  	Comparator() interface{}
    51  }
    52  
    53  // getLockPath returns the lock path representation of the given path.
    54  func getLockPath(path string) string {
    55  	return path + ".lock"
    56  }
    57  
    58  type lockOwner struct {
    59  	created time.Time
    60  	id      uuid.UUID
    61  }
    62  
    63  type pathLocks struct {
    64  	mutex     lock.RWMutex
    65  	lockPaths map[string]lockOwner
    66  }
    67  
    68  func init() {
    69  	debug.RegisterStatusObject("kvstore-locks", &kvstoreLocks)
    70  }
    71  
    72  // DebugStatus implements debug.StatusObject to provide debug status collection
    73  // ability
    74  func (pl *pathLocks) DebugStatus() string {
    75  	pl.mutex.RLock()
    76  	str := spew.Sdump(pl.lockPaths)
    77  	pl.mutex.RUnlock()
    78  	return str
    79  }
    80  
    81  func (pl *pathLocks) runGC() {
    82  	pl.mutex.Lock()
    83  	for path, owner := range pl.lockPaths {
    84  		if time.Since(owner.created) > staleLockTimeout {
    85  			log.WithField("path", path).Error("Forcefully unlocking local kvstore lock")
    86  			delete(pl.lockPaths, path)
    87  		}
    88  	}
    89  	pl.mutex.Unlock()
    90  }
    91  
    92  func (pl *pathLocks) lock(ctx context.Context, path string) (id uuid.UUID, err error) {
    93  	for {
    94  		pl.mutex.Lock()
    95  		if _, ok := pl.lockPaths[path]; !ok {
    96  			id = uuidfactor.NewUUID()
    97  			pl.lockPaths[path] = lockOwner{
    98  				created: time.Now(),
    99  				id:      id,
   100  			}
   101  			pl.mutex.Unlock()
   102  			return
   103  		}
   104  		pl.mutex.Unlock()
   105  
   106  		select {
   107  		case <-time.After(time.Duration(10) * time.Millisecond):
   108  		case <-ctx.Done():
   109  			err = fmt.Errorf("lock was cancelled: %s", ctx.Err())
   110  			return
   111  		}
   112  	}
   113  }
   114  
   115  func (pl *pathLocks) unlock(path string, id uuid.UUID) {
   116  	pl.mutex.Lock()
   117  	if owner, ok := pl.lockPaths[path]; ok && uuid.Equal(owner.id, id) {
   118  		delete(pl.lockPaths, path)
   119  	}
   120  	pl.mutex.Unlock()
   121  }
   122  
   123  // Lock is a lock return by LockPath
   124  type Lock struct {
   125  	path   string
   126  	id     uuid.UUID
   127  	kvLock KVLocker
   128  }
   129  
   130  // LockPath locks the specified path. The key for the lock is not the path
   131  // provided itself but the path with a suffix of ".lock" appended. The lock
   132  // returned also contains a patch specific local Mutex which will be held.
   133  //
   134  // It is required to call Unlock() on the returned Lock to unlock
   135  func LockPath(ctx context.Context, backend BackendOperations, path string) (l *Lock, err error) {
   136  	id, err := kvstoreLocks.lock(ctx, path)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	lock, err := backend.LockPath(ctx, path)
   142  	if err != nil {
   143  		kvstoreLocks.unlock(path, id)
   144  		Trace("Failed to lock", err, logrus.Fields{fieldKey: path})
   145  		err = fmt.Errorf("error while locking path %s: %s", path, err)
   146  		return nil, err
   147  	}
   148  
   149  	Trace("Successful lock", err, logrus.Fields{fieldKey: path})
   150  	return &Lock{kvLock: lock, path: path, id: id}, err
   151  }
   152  
   153  // RunLockGC inspects all local kvstore locks to determine whether they have
   154  // been held longer than the stale lock timeout, and if so, unlocks them
   155  // forceably.
   156  func RunLockGC() {
   157  	kvstoreLocks.runGC()
   158  }
   159  
   160  // Unlock unlocks a lock
   161  func (l *Lock) Unlock() error {
   162  	if l == nil {
   163  		return nil
   164  	}
   165  
   166  	// Unlock kvstore mutex first
   167  	err := l.kvLock.Unlock()
   168  	if err != nil {
   169  		log.WithError(err).WithField("path", l.path).Error("Unable to unlock kvstore lock")
   170  	}
   171  
   172  	// unlock local lock even if kvstore cannot be unlocked
   173  	kvstoreLocks.unlock(l.path, l.id)
   174  	Trace("Unlocked", nil, logrus.Fields{fieldKey: l.path})
   175  
   176  	return err
   177  }
   178  
   179  func (l *Lock) Comparator() interface{} {
   180  	return l.kvLock.Comparator()
   181  }