You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ims/util/db/mysql/lock.go

93 lines
2.2 KiB

package mysql
import (
"database/sql"
"fmt"
"ims/util/backoff"
"ims/util/db"
"gorm.io/gorm"
)
// SQL statements for MySQL advisory locks.
// https://dev.mysql.com/doc/refman/8.4/en/locking-functions.html
const (
// GET_LOCK(str, timeout) → int (1: lock acquired, 0: lock not acquired, NULL: an error occurred).
sqlGetLock = "SELECT GET_LOCK('%s', 0)"
// RELEASE_LOCK(str) → int (1: lock released, 0: lock not released, NULL: lock does not exist).
sqlReleaseLock = "SELECT RELEASE_LOCK('%s')"
)
// lock represents a MySQL advisory lock.
type lock struct {
tx *gorm.DB
lockKey string
}
func (a *lock) execute(sqlstr string) (bool, error) {
var result sql.NullInt64
if err := a.tx.Raw(sqlstr).Scan(&result).Error; err == nil {
if !result.Valid {
return false, db.ErrAcquireLock
}
switch result.Int64 {
case 1:
return true, nil
case 0:
return false, db.ErrAcquireLock
}
}
return false, fmt.Errorf("%w: %s", db.ErrExecSQL, sqlstr)
}
func (a *lock) acquire() (func() error, error) {
sqlstr := fmt.Sprintf(sqlGetLock, a.lockKey)
ok, err := a.execute(sqlstr)
if err != nil || !ok {
return nil, fmt.Errorf("%w for key %s: %v", db.ErrAcquireLock, a.lockKey, err)
}
return a.release, nil
}
func (a *lock) release() error {
sqlstr := fmt.Sprintf(sqlReleaseLock, a.lockKey)
success, err := a.execute(sqlstr)
if err != nil || !success {
return fmt.Errorf("%w for key %s: %v", db.ErrReleaseLock, a.lockKey, err)
}
return nil
}
// acquire acquires a MySQL advisory lock.
func acquire(tx *gorm.DB, lockKey string) (func() error, error) {
lock := &lock{tx: tx, lockKey: lockKey}
return lock.acquire()
}
// Acquire acquires a MySQL advisory lock.
// Returns a release function and an error.
//
// It's the responsibility of the caller to release the lock by calling the release function.
func AcquireLock(tx *gorm.DB, lockKey string, options *backoff.Options) (release func() error, err error) {
if options == nil {
release, err = acquire(tx, lockKey)
return
}
acquireFunc := func() error {
releaseFn, acquireErr := acquire(tx, lockKey)
if acquireErr != nil {
return acquireErr
}
release = releaseFn
return nil
}
err = backoff.Retry(acquireFunc, func(o *backoff.Options) {
*o = *options
})
return release, err
}