initial commit
This commit is contained in:
98
pkg/locker/locker.go
Normal file
98
pkg/locker/locker.go
Normal file
@@ -0,0 +1,98 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user