github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/lock/lock.go (about)

     1  package lock
     2  
     3  import (
     4  	"database/sql"
     5  	"errors"
     6  	"fmt"
     7  	"hash/crc32"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  
    12  	"code.cloudfoundry.org/lager"
    13  )
    14  
    15  const (
    16  	LockTypeResourceConfigChecking = iota
    17  	LockTypeBuildTracking
    18  	LockTypeBatch
    19  	LockTypeVolumeCreating
    20  	LockTypeContainerCreating
    21  	LockTypeDatabaseMigration
    22  	LockTypeActiveTasks
    23  	LockTypeResourceScanning
    24  	LockTypeJobScheduling
    25  )
    26  
    27  var ErrLostLock = errors.New("lock was lost while held, possibly due to connection breakage")
    28  
    29  func NewBuildTrackingLockID(buildID int) LockID {
    30  	return LockID{LockTypeBuildTracking, buildID}
    31  }
    32  
    33  func NewResourceConfigCheckingLockID(resourceConfigID int) LockID {
    34  	return LockID{LockTypeResourceConfigChecking, resourceConfigID}
    35  }
    36  
    37  func NewTaskLockID(taskName string) LockID {
    38  	return LockID{LockTypeBatch, lockIDFromString(taskName)}
    39  }
    40  
    41  func NewVolumeCreatingLockID(volumeID int) LockID {
    42  	return LockID{LockTypeVolumeCreating, volumeID}
    43  }
    44  
    45  func NewDatabaseMigrationLockID() LockID {
    46  	return LockID{LockTypeDatabaseMigration}
    47  }
    48  
    49  func NewActiveTasksLockID() LockID {
    50  	return LockID{LockTypeActiveTasks}
    51  }
    52  
    53  func NewResourceScanningLockID() LockID {
    54  	return LockID{LockTypeResourceScanning}
    55  }
    56  
    57  func NewJobSchedulingLockID(jobID int) LockID {
    58  	return LockID{LockTypeJobScheduling, jobID}
    59  }
    60  
    61  //go:generate counterfeiter . LockFactory
    62  
    63  type LockFactory interface {
    64  	Acquire(logger lager.Logger, ids LockID) (Lock, bool, error)
    65  }
    66  
    67  type lockFactory struct {
    68  	db           LockDB
    69  	locks        lockRepo
    70  	acquireMutex *sync.Mutex
    71  
    72  	acquireFunc LogFunc
    73  	releaseFunc LogFunc
    74  }
    75  
    76  type LogFunc func(logger lager.Logger, id LockID)
    77  
    78  func NewLockFactory(
    79  	conn *sql.DB,
    80  	acquire LogFunc,
    81  	release LogFunc,
    82  ) LockFactory {
    83  	return &lockFactory{
    84  		db: &lockDB{
    85  			conn:  conn,
    86  			mutex: &sync.Mutex{},
    87  		},
    88  		acquireFunc: acquire,
    89  		releaseFunc: release,
    90  		locks: lockRepo{
    91  			locks: map[string]bool{},
    92  			mutex: &sync.Mutex{},
    93  		},
    94  		acquireMutex: &sync.Mutex{},
    95  	}
    96  }
    97  
    98  func NewTestLockFactory(db LockDB) LockFactory {
    99  	return &lockFactory{
   100  		db: db,
   101  		locks: lockRepo{
   102  			locks: map[string]bool{},
   103  			mutex: &sync.Mutex{},
   104  		},
   105  		acquireMutex: &sync.Mutex{},
   106  		acquireFunc:  func(logger lager.Logger, id LockID) {},
   107  		releaseFunc:  func(logger lager.Logger, id LockID) {},
   108  	}
   109  }
   110  
   111  func (f *lockFactory) Acquire(logger lager.Logger, id LockID) (Lock, bool, error) {
   112  	l := &lock{
   113  		logger:       logger,
   114  		db:           f.db,
   115  		id:           id,
   116  		locks:        f.locks,
   117  		acquireMutex: f.acquireMutex,
   118  		acquired:     f.acquireFunc,
   119  		released:     f.releaseFunc,
   120  	}
   121  
   122  	acquired, err := l.Acquire()
   123  	if err != nil {
   124  		return nil, false, err
   125  	}
   126  
   127  	if !acquired {
   128  		return nil, false, nil
   129  	}
   130  
   131  	return l, true, nil
   132  }
   133  
   134  //go:generate counterfeiter . Lock
   135  
   136  type Lock interface {
   137  	Release() error
   138  }
   139  
   140  // NoopLock is a fake lock for use when a lock is conditionally acquired.
   141  type NoopLock struct{}
   142  
   143  // Release does nothing. Successfully.
   144  func (NoopLock) Release() error { return nil }
   145  
   146  //go:generate counterfeiter . LockDB
   147  
   148  type LockDB interface {
   149  	Acquire(id LockID) (bool, error)
   150  	Release(id LockID) (bool, error)
   151  }
   152  
   153  type lock struct {
   154  	id LockID
   155  
   156  	logger       lager.Logger
   157  	db           LockDB
   158  	locks        lockRepo
   159  	acquireMutex *sync.Mutex
   160  
   161  	acquired LogFunc
   162  	released LogFunc
   163  }
   164  
   165  func (l *lock) Acquire() (bool, error) {
   166  	l.acquireMutex.Lock()
   167  	defer l.acquireMutex.Unlock()
   168  
   169  	logger := l.logger.Session("acquire", lager.Data{"id": l.id})
   170  
   171  	if l.locks.IsRegistered(l.id) {
   172  		logger.Debug("not-acquired-already-held-locally")
   173  		return false, nil
   174  	}
   175  
   176  	acquired, err := l.db.Acquire(l.id)
   177  	if err != nil {
   178  		logger.Error("failed-to-register-in-db", err)
   179  		return false, err
   180  	}
   181  
   182  	if !acquired {
   183  		logger.Debug("not-acquired-already-held-in-db")
   184  		return false, nil
   185  	}
   186  
   187  	l.locks.Register(l.id)
   188  
   189  	l.acquired(logger, l.id)
   190  
   191  	return true, nil
   192  }
   193  
   194  func (l *lock) Release() error {
   195  	logger := l.logger.Session("release", lager.Data{"id": l.id})
   196  
   197  	released, err := l.db.Release(l.id)
   198  	if err != nil {
   199  		logger.Error("failed-to-release-in-db-but-continuing-anyway", err)
   200  	}
   201  
   202  	l.locks.Unregister(l.id)
   203  
   204  	if !released {
   205  		logger.Error("failed-to-release", ErrLostLock)
   206  		return ErrLostLock
   207  	}
   208  
   209  	l.released(logger, l.id)
   210  
   211  	return nil
   212  }
   213  
   214  type lockDB struct {
   215  	conn  *sql.DB
   216  	mutex *sync.Mutex
   217  }
   218  
   219  func (db *lockDB) Acquire(id LockID) (bool, error) {
   220  	db.mutex.Lock()
   221  	defer db.mutex.Unlock()
   222  
   223  	var acquired bool
   224  	err := db.conn.QueryRow(`SELECT pg_try_advisory_lock(`+id.toDBParams()+`)`, id.toDBArgs()...).Scan(&acquired)
   225  	if err != nil {
   226  		return false, err
   227  	}
   228  
   229  	return acquired, nil
   230  }
   231  
   232  func (db *lockDB) Release(id LockID) (bool, error) {
   233  	db.mutex.Lock()
   234  	defer db.mutex.Unlock()
   235  
   236  	var released bool
   237  	err := db.conn.QueryRow(`SELECT pg_advisory_unlock(`+id.toDBParams()+`)`, id.toDBArgs()...).Scan(&released)
   238  	if err != nil {
   239  		return false, err
   240  	}
   241  
   242  	return released, nil
   243  }
   244  
   245  type lockRepo struct {
   246  	locks map[string]bool
   247  	mutex *sync.Mutex
   248  }
   249  
   250  func (lr lockRepo) IsRegistered(id LockID) bool {
   251  	lr.mutex.Lock()
   252  	defer lr.mutex.Unlock()
   253  
   254  	if _, ok := lr.locks[id.toKey()]; ok {
   255  		return true
   256  	}
   257  	return false
   258  }
   259  
   260  func (lr lockRepo) Register(id LockID) {
   261  	lr.mutex.Lock()
   262  	defer lr.mutex.Unlock()
   263  
   264  	lr.locks[id.toKey()] = true
   265  }
   266  
   267  func (lr lockRepo) Unregister(id LockID) {
   268  	lr.mutex.Lock()
   269  	defer lr.mutex.Unlock()
   270  
   271  	delete(lr.locks, id.toKey())
   272  }
   273  
   274  type LockID []int
   275  
   276  func (l LockID) toKey() string {
   277  	s := []string{}
   278  	for i := range l {
   279  		s = append(s, strconv.Itoa(l[i]))
   280  	}
   281  	return strings.Join(s, "+")
   282  }
   283  
   284  func (l LockID) toDBParams() string {
   285  	s := []string{}
   286  	for i := range l {
   287  		s = append(s, fmt.Sprintf("$%d", i+1))
   288  	}
   289  
   290  	return strings.Join(s, ",")
   291  }
   292  
   293  func (l LockID) toDBArgs() []interface{} {
   294  	result := []interface{}{}
   295  	for i := range l {
   296  		result = append(result, l[i])
   297  	}
   298  
   299  	return result
   300  }
   301  
   302  func lockIDFromString(taskName string) int {
   303  	return int(int32(crc32.ChecksumIEEE([]byte(taskName))))
   304  }