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 }