99 lines
2.1 KiB
Go
99 lines
2.1 KiB
Go
package locker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
type locker struct {
|
|
client *redis.Client
|
|
logger zerolog.Logger
|
|
}
|
|
|
|
func NewLocker(client *redis.Client, logger zerolog.Logger) Locker {
|
|
return &locker{
|
|
client: client,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (l locker) Lock(ctx context.Context, id string, ttl time.Duration) (bool, error) {
|
|
status := l.client.SetNX(ctx, id, "locked", ttl)
|
|
if err := status.Err(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Return whether the lock was acquired
|
|
return status.Val(), nil
|
|
}
|
|
|
|
func (l locker) Unlock(ctx context.Context, id string) error {
|
|
// Delete the lock by its ID
|
|
_, err := l.client.Del(ctx, id).Result()
|
|
return err
|
|
}
|
|
|
|
// WithLock acquires a lock for a specific vendor, executes the provided function,
|
|
// and ensures that the lock is released afterward. If any error occurs, it returns the
|
|
// error, preserving the context of the lock operation.
|
|
func (l locker) WithLock(
|
|
ctx context.Context,
|
|
lockKey string,
|
|
lockTime time.Duration,
|
|
fn func(context.Context) error,
|
|
) error {
|
|
lg := l.logger.With().
|
|
Str("method", "WithLock").
|
|
Str("key", lockKey).
|
|
Logger()
|
|
|
|
maxRetries := 5
|
|
retryDelay := 10 * time.Millisecond //TODO: Replace with proper logging
|
|
|
|
var locked bool
|
|
var acquireErr error
|
|
|
|
for attempt := 0; attempt <= maxRetries; attempt++ {
|
|
locked, acquireErr = l.Lock(ctx, lockKey, lockTime)
|
|
|
|
if locked {
|
|
lg.Info().Msg("LockAcquired")
|
|
break
|
|
}
|
|
|
|
if attempt == maxRetries {
|
|
break
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(retryDelay):
|
|
retryDelay *= 2 // Exponential backoff
|
|
}
|
|
}
|
|
|
|
if !locked || acquireErr != nil {
|
|
lg.Error().Err(acquireErr).Msg("LockErr")
|
|
return NewLockError(lockKey, uint32(maxRetries), acquireErr)
|
|
}
|
|
|
|
fnErr := fn(ctx)
|
|
if fnErr != nil {
|
|
return fmt.Errorf("failed to execute function for %s due to error %v", lockKey, fnErr)
|
|
}
|
|
|
|
if unlockErr := l.Unlock(ctx, lockKey); unlockErr != nil {
|
|
return fmt.Errorf("failed to unlock lock for %s: %v", lockKey, unlockErr)
|
|
}
|
|
|
|
lg.Info().Msg("Unlocked")
|
|
|
|
return nil
|
|
}
|