package utils import ( "context" "errors" "time" ) var ErrRedisNotInitialized = errors.New("redis client is not initialized, set redis_enable=true and restart") const redisLockReleaseScript = ` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end ` // AcquireRedisLock tries to create a distributed lock with key and value. // It returns true when lock is acquired, false when lock already exists. func AcquireRedisLock(key, value string) (bool, error) { if RedisClient == nil { return false, ErrRedisNotInitialized } if key == "" || value == "" { return false, errors.New("key and value must not be empty") } lockTTLMS := redisInt("redis_lock_ttl_ms", 30000) opTimeoutMS := redisInt("redis_lock_op_timeout_ms", 1000) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opTimeoutMS)*time.Millisecond) defer cancel() return RedisClient.SetNX(ctx, key, value, time.Duration(lockTTLMS)*time.Millisecond).Result() } // ReleaseRedisLock releases lock only when key currently matches value. // It returns true when released successfully. func ReleaseRedisLock(key, value string) (bool, error) { if RedisClient == nil { return false, ErrRedisNotInitialized } if key == "" || value == "" { return false, errors.New("key and value must not be empty") } opTimeoutMS := redisInt("redis_lock_op_timeout_ms", 1000) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opTimeoutMS)*time.Millisecond) defer cancel() n, err := RedisClient.Eval(ctx, redisLockReleaseScript, []string{key}, value).Int64() if err != nil { return false, err } return n == 1, nil }