package redis import ( "context" "time" "forge.cadoles.com/cadoles/bouncer/internal/lock" "github.com/bsm/redislock" "github.com/pkg/errors" "github.com/redis/go-redis/v9" "gitlab.com/wpetit/goweb/logger" ) type Locker struct { client redis.UniversalClient maxRetries int } // WithLock implements lock.Locker. func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration, fn func(ctx context.Context) error) error { locker := redislock.New(l.client) backoff := redislock.ExponentialBackoff(time.Second, timeout*2) ctx = logger.With(ctx, logger.F("lockTimeout", timeout), logger.F("lockKey", key)) logger.Debug(ctx, "acquiring lock") err := retryWithBackoff(ctx, l.maxRetries, func(ctx context.Context) error { lock, err := locker.Obtain(ctx, key, timeout, &redislock.Options{ RetryStrategy: backoff, }) if err != nil { return errors.WithStack(err) } logger.Debug(ctx, "lock obtained") defer func() { if err := lock.Release(ctx); err != nil { logger.Error(ctx, "could not release lock", logger.CapturedE(errors.WithStack(err))) } logger.Debug(ctx, "lock released") }() if err := fn(ctx); err != nil { return errors.WithStack(err) } return nil }) if err != nil { return errors.WithStack(err) } return nil } func NewLocker(client redis.UniversalClient, maxRetries int) *Locker { return &Locker{ client: client, maxRetries: maxRetries, } } var _ lock.Locker = &Locker{}