338 lines
6.8 KiB
Go
338 lines
6.8 KiB
Go
package locking
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/rand"
|
||
|
|
"encoding/hex"
|
||
|
|
"fmt"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// LockBackend defines the interface for lock backends
|
||
|
|
type LockBackend interface {
|
||
|
|
// Acquire attempts to acquire a lock
|
||
|
|
Acquire(key string, ttl time.Duration) (string, error)
|
||
|
|
// Release releases a lock
|
||
|
|
Release(key string, token string) error
|
||
|
|
// Renew renews a lock's TTL
|
||
|
|
Renew(key string, token string, ttl time.Duration) error
|
||
|
|
// IsLocked checks if a lock is held
|
||
|
|
IsLocked(key string) (bool, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// LocalLockBackend is a fallback in-memory lock backend
|
||
|
|
type LocalLockBackend struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
locks map[string]string
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewLocalLockBackend creates a new local lock backend
|
||
|
|
func NewLocalLockBackend() *LocalLockBackend {
|
||
|
|
return &LocalLockBackend{
|
||
|
|
locks: make(map[string]string),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Acquire acquires a lock locally
|
||
|
|
func (lb *LocalLockBackend) Acquire(key string, ttl time.Duration) (string, error) {
|
||
|
|
lb.mu.Lock()
|
||
|
|
defer lb.mu.Unlock()
|
||
|
|
|
||
|
|
if _, exists := lb.locks[key]; exists {
|
||
|
|
return "", fmt.Errorf("lock already held")
|
||
|
|
}
|
||
|
|
|
||
|
|
token := generateToken()
|
||
|
|
lb.locks[key] = token
|
||
|
|
|
||
|
|
return token, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Release releases a lock locally
|
||
|
|
func (lb *LocalLockBackend) Release(key string, token string) error {
|
||
|
|
lb.mu.Lock()
|
||
|
|
defer lb.mu.Unlock()
|
||
|
|
|
||
|
|
if held, exists := lb.locks[key]; !exists || held != token {
|
||
|
|
return fmt.Errorf("lock not held by token")
|
||
|
|
}
|
||
|
|
|
||
|
|
delete(lb.locks, key)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Renew renews a lock locally (no-op for local backend)
|
||
|
|
func (lb *LocalLockBackend) Renew(key string, token string, ttl time.Duration) error {
|
||
|
|
lb.mu.RLock()
|
||
|
|
defer lb.mu.RUnlock()
|
||
|
|
|
||
|
|
if held, exists := lb.locks[key]; !exists || held != token {
|
||
|
|
return fmt.Errorf("lock not held by token")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsLocked checks if a lock is held locally
|
||
|
|
func (lb *LocalLockBackend) IsLocked(key string) (bool, error) {
|
||
|
|
lb.mu.RLock()
|
||
|
|
defer lb.mu.RUnlock()
|
||
|
|
|
||
|
|
_, exists := lb.locks[key]
|
||
|
|
return exists, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// DistributedLock represents a distributed lock
|
||
|
|
type DistributedLock struct {
|
||
|
|
key string
|
||
|
|
token string
|
||
|
|
backend LockBackend
|
||
|
|
mu sync.RWMutex
|
||
|
|
acquired bool
|
||
|
|
acquiredAt time.Time
|
||
|
|
ttl time.Duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewDistributedLock creates a new distributed lock
|
||
|
|
func NewDistributedLock(key string, backend LockBackend, ttl time.Duration) *DistributedLock {
|
||
|
|
if ttl == 0 {
|
||
|
|
ttl = 30 * time.Second // Default TTL
|
||
|
|
}
|
||
|
|
|
||
|
|
return &DistributedLock{
|
||
|
|
key: key,
|
||
|
|
backend: backend,
|
||
|
|
ttl: ttl,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Acquire acquires the lock with timeout
|
||
|
|
func (dl *DistributedLock) Acquire(timeout time.Duration) error {
|
||
|
|
if timeout == 0 {
|
||
|
|
timeout = 5 * time.Second // Default timeout
|
||
|
|
}
|
||
|
|
|
||
|
|
deadline := time.Now().Add(timeout)
|
||
|
|
|
||
|
|
for {
|
||
|
|
token, err := dl.backend.Acquire(dl.key, dl.ttl)
|
||
|
|
if err == nil {
|
||
|
|
dl.mu.Lock()
|
||
|
|
dl.token = token
|
||
|
|
dl.acquired = true
|
||
|
|
dl.acquiredAt = time.Now()
|
||
|
|
dl.mu.Unlock()
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
if time.Now().After(deadline) {
|
||
|
|
return fmt.Errorf("lock acquisition timeout")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Back off before retrying
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Release releases the lock
|
||
|
|
func (dl *DistributedLock) Release() error {
|
||
|
|
dl.mu.Lock()
|
||
|
|
defer dl.mu.Unlock()
|
||
|
|
|
||
|
|
if !dl.acquired {
|
||
|
|
return fmt.Errorf("lock not acquired")
|
||
|
|
}
|
||
|
|
|
||
|
|
err := dl.backend.Release(dl.key, dl.token)
|
||
|
|
if err == nil {
|
||
|
|
dl.acquired = false
|
||
|
|
dl.token = ""
|
||
|
|
}
|
||
|
|
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Renew renews the lock's TTL
|
||
|
|
func (dl *DistributedLock) Renew() error {
|
||
|
|
dl.mu.RLock()
|
||
|
|
defer dl.mu.RUnlock()
|
||
|
|
|
||
|
|
if !dl.acquired {
|
||
|
|
return fmt.Errorf("lock not acquired")
|
||
|
|
}
|
||
|
|
|
||
|
|
return dl.backend.Renew(dl.key, dl.token, dl.ttl)
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsAcquired checks if the lock is currently acquired
|
||
|
|
func (dl *DistributedLock) IsAcquired() bool {
|
||
|
|
dl.mu.RLock()
|
||
|
|
defer dl.mu.RUnlock()
|
||
|
|
|
||
|
|
return dl.acquired
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetAcquiredAt returns when the lock was acquired
|
||
|
|
func (dl *DistributedLock) GetAcquiredAt() time.Time {
|
||
|
|
dl.mu.RLock()
|
||
|
|
defer dl.mu.RUnlock()
|
||
|
|
|
||
|
|
return dl.acquiredAt
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetHoldDuration returns how long the lock has been held
|
||
|
|
func (dl *DistributedLock) GetHoldDuration() time.Duration {
|
||
|
|
dl.mu.RLock()
|
||
|
|
defer dl.mu.RUnlock()
|
||
|
|
|
||
|
|
if !dl.acquired {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
return time.Since(dl.acquiredAt)
|
||
|
|
}
|
||
|
|
|
||
|
|
// LockManager manages multiple distributed locks
|
||
|
|
type LockManager struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
backend LockBackend
|
||
|
|
locks map[string]*DistributedLock
|
||
|
|
lockTTL time.Duration
|
||
|
|
stats *LockStats
|
||
|
|
}
|
||
|
|
|
||
|
|
// LockStats tracks lock statistics
|
||
|
|
type LockStats struct {
|
||
|
|
TotalAcquisitions int
|
||
|
|
TotalReleases int
|
||
|
|
FailedAcquisitions int
|
||
|
|
ActiveLocks int
|
||
|
|
AverageLockTime time.Duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewLockManager creates a new lock manager
|
||
|
|
func NewLockManager(backend LockBackend, lockTTL time.Duration) *LockManager {
|
||
|
|
if lockTTL == 0 {
|
||
|
|
lockTTL = 30 * time.Second
|
||
|
|
}
|
||
|
|
|
||
|
|
return &LockManager{
|
||
|
|
backend: backend,
|
||
|
|
locks: make(map[string]*DistributedLock),
|
||
|
|
lockTTL: lockTTL,
|
||
|
|
stats: &LockStats{
|
||
|
|
TotalAcquisitions: 0,
|
||
|
|
TotalReleases: 0,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// AcquireLock acquires or retrieves an existing lock
|
||
|
|
func (lm *LockManager) AcquireLock(key string, timeout time.Duration) error {
|
||
|
|
lm.mu.Lock()
|
||
|
|
defer lm.mu.Unlock()
|
||
|
|
|
||
|
|
// Check if lock already exists and is acquired
|
||
|
|
if lock, exists := lm.locks[key]; exists && lock.IsAcquired() {
|
||
|
|
return fmt.Errorf("lock already acquired by this manager")
|
||
|
|
}
|
||
|
|
|
||
|
|
lock := NewDistributedLock(key, lm.backend, lm.lockTTL)
|
||
|
|
err := lock.Acquire(timeout)
|
||
|
|
if err != nil {
|
||
|
|
lm.stats.FailedAcquisitions++
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
lm.locks[key] = lock
|
||
|
|
lm.stats.TotalAcquisitions++
|
||
|
|
lm.stats.ActiveLocks++
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReleaseLock releases a lock
|
||
|
|
func (lm *LockManager) ReleaseLock(key string) error {
|
||
|
|
lm.mu.Lock()
|
||
|
|
defer lm.mu.Unlock()
|
||
|
|
|
||
|
|
lock, exists := lm.locks[key]
|
||
|
|
if !exists {
|
||
|
|
return fmt.Errorf("lock not found")
|
||
|
|
}
|
||
|
|
|
||
|
|
err := lock.Release()
|
||
|
|
if err == nil {
|
||
|
|
lm.stats.TotalReleases++
|
||
|
|
lm.stats.ActiveLocks--
|
||
|
|
delete(lm.locks, key)
|
||
|
|
}
|
||
|
|
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// RenewLock renews a lock
|
||
|
|
func (lm *LockManager) RenewLock(key string) error {
|
||
|
|
lm.mu.RLock()
|
||
|
|
defer lm.mu.RUnlock()
|
||
|
|
|
||
|
|
lock, exists := lm.locks[key]
|
||
|
|
if !exists {
|
||
|
|
return fmt.Errorf("lock not found")
|
||
|
|
}
|
||
|
|
|
||
|
|
return lock.Renew()
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetLockStats returns lock statistics
|
||
|
|
func (lm *LockManager) GetLockStats() *LockStats {
|
||
|
|
lm.mu.RLock()
|
||
|
|
defer lm.mu.RUnlock()
|
||
|
|
|
||
|
|
stats := *lm.stats
|
||
|
|
return &stats
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetActiveLocks returns list of active lock keys
|
||
|
|
func (lm *LockManager) GetActiveLocks() []string {
|
||
|
|
lm.mu.RLock()
|
||
|
|
defer lm.mu.RUnlock()
|
||
|
|
|
||
|
|
keys := make([]string, 0, len(lm.locks))
|
||
|
|
for key, lock := range lm.locks {
|
||
|
|
if lock.IsAcquired() {
|
||
|
|
keys = append(keys, key)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return keys
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReleaseAll releases all locks
|
||
|
|
func (lm *LockManager) ReleaseAll() error {
|
||
|
|
lm.mu.Lock()
|
||
|
|
defer lm.mu.Unlock()
|
||
|
|
|
||
|
|
var lastErr error
|
||
|
|
for key, lock := range lm.locks {
|
||
|
|
if lock.IsAcquired() {
|
||
|
|
err := lock.Release()
|
||
|
|
if err != nil {
|
||
|
|
lastErr = err
|
||
|
|
}
|
||
|
|
delete(lm.locks, key)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
lm.stats.ActiveLocks = 0
|
||
|
|
return lastErr
|
||
|
|
}
|
||
|
|
|
||
|
|
// generateToken generates a random token for lock identification
|
||
|
|
func generateToken() string {
|
||
|
|
b := make([]byte, 16)
|
||
|
|
rand.Read(b)
|
||
|
|
return hex.EncodeToString(b)
|
||
|
|
}
|