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.
69 lines
1.6 KiB
69 lines
1.6 KiB
2 months ago
|
package db
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"hash/fnv"
|
||
|
"ims/util/backoff"
|
||
|
|
||
|
"gorm.io/gorm"
|
||
|
)
|
||
|
|
||
|
// SQL statements for PostgreSQL advisory locks.
|
||
|
// https://www.postgresql.org/docs/16/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
|
||
|
const (
|
||
|
// pg_try_advisory_xact_lock ( key bigint ) → boolean.
|
||
|
pgsqlTryAdvisoryXactLock = "SELECT pg_try_advisory_xact_lock(?)"
|
||
|
)
|
||
|
|
||
|
// pgsqlLock represents a PostgreSQL transaction-level advisory pgsqlLock.
|
||
|
type pgsqlLock struct {
|
||
|
tx *gorm.DB
|
||
|
lockKey string
|
||
|
}
|
||
|
|
||
|
func (p *pgsqlLock) execute(sqlstr string, args ...any) (bool, error) {
|
||
|
var result bool
|
||
|
if err := p.tx.Raw(sqlstr, args...).Scan(&result).Error; err == nil && result {
|
||
|
return true, nil
|
||
|
}
|
||
|
return false, fmt.Errorf("%w: %s", ErrExecSQL, sqlstr)
|
||
|
}
|
||
|
|
||
|
func (p *pgsqlLock) acquire() error {
|
||
|
key := generateLockKey(p.lockKey)
|
||
|
ok, err := p.execute(pgsqlTryAdvisoryXactLock, key)
|
||
|
if err != nil || !ok {
|
||
|
return fmt.Errorf("%w for key %s: %v", ErrAcquireLock, p.lockKey, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func generateLockKey(s string) int64 {
|
||
|
hasher := fnv.New64a()
|
||
|
hasher.Write([]byte(s))
|
||
|
hash := hasher.Sum64()
|
||
|
return int64(hash)
|
||
|
}
|
||
|
|
||
|
// AcquireXact acquires a PostgreSQL transaction-level advisory lock.
|
||
|
// The caller is responsible for ensuring that a transaction is active,
|
||
|
// and that the lock is released after use.
|
||
|
func AcquireXact(tx *gorm.DB, lockKey string, opts *backoff.Options) (release func() error, err error) {
|
||
|
l := &pgsqlLock{tx: tx, lockKey: lockKey}
|
||
|
|
||
|
if opts == nil {
|
||
|
err = l.acquire()
|
||
|
} else {
|
||
|
err = backoff.Retry(
|
||
|
func() error { return l.acquire() },
|
||
|
func(o *backoff.Options) { *o = *opts },
|
||
|
)
|
||
|
}
|
||
|
|
||
|
release = func() error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|