2016-10-27 08:59:52 -04:00
|
|
|
require 'securerandom'
|
|
|
|
|
2016-03-10 04:41:16 -05:00
|
|
|
module Gitlab
|
|
|
|
# This class implements an 'exclusive lease'. We call it a 'lease'
|
|
|
|
# because it has a set expiry time. We call it 'exclusive' because only
|
|
|
|
# one caller may obtain a lease for a given key at a time. The
|
|
|
|
# implementation is intended to work across GitLab processes and across
|
2016-10-27 08:59:52 -04:00
|
|
|
# servers. It is a cheap alternative to using SQL queries and updates:
|
2016-03-10 04:41:16 -05:00
|
|
|
# you do not need to change the SQL schema to start using
|
|
|
|
# ExclusiveLease.
|
2016-03-10 06:37:14 -05:00
|
|
|
#
|
2016-03-10 04:41:16 -05:00
|
|
|
class ExclusiveLease
|
2017-06-23 01:36:19 -04:00
|
|
|
LUA_CANCEL_SCRIPT = <<~EOS.freeze
|
2016-10-27 08:59:52 -04:00
|
|
|
local key, uuid = KEYS[1], ARGV[1]
|
|
|
|
if redis.call("get", key) == uuid then
|
|
|
|
redis.call("del", key)
|
|
|
|
end
|
|
|
|
EOS
|
|
|
|
|
2017-06-23 01:36:19 -04:00
|
|
|
LUA_RENEW_SCRIPT = <<~EOS.freeze
|
|
|
|
local key, uuid, ttl = KEYS[1], ARGV[1], ARGV[2]
|
|
|
|
if redis.call("get", key) == uuid then
|
|
|
|
redis.call("expire", key, ttl)
|
|
|
|
return uuid
|
|
|
|
end
|
|
|
|
EOS
|
|
|
|
|
2017-09-04 13:55:04 -04:00
|
|
|
def self.get_uuid(key)
|
|
|
|
Gitlab::Redis::SharedState.with do |redis|
|
|
|
|
redis.get(redis_shared_state_key(key)) || false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-27 08:59:52 -04:00
|
|
|
def self.cancel(key, uuid)
|
2017-07-10 23:35:47 -04:00
|
|
|
Gitlab::Redis::SharedState.with do |redis|
|
|
|
|
redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_shared_state_key(key)], argv: [uuid])
|
2016-10-27 08:59:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-10 23:35:47 -04:00
|
|
|
def self.redis_shared_state_key(key)
|
2016-10-27 08:59:52 -04:00
|
|
|
"gitlab:exclusive_lease:#{key}"
|
|
|
|
end
|
|
|
|
|
2017-09-04 13:55:04 -04:00
|
|
|
def initialize(key, uuid: nil, timeout:)
|
2017-07-10 23:35:47 -04:00
|
|
|
@redis_shared_state_key = self.class.redis_shared_state_key(key)
|
2016-10-27 08:59:52 -04:00
|
|
|
@timeout = timeout
|
2017-09-04 13:55:04 -04:00
|
|
|
@uuid = uuid || SecureRandom.uuid
|
2016-03-10 04:41:16 -05:00
|
|
|
end
|
|
|
|
|
2016-10-27 08:59:52 -04:00
|
|
|
# Try to obtain the lease. Return lease UUID on success,
|
2016-03-10 04:41:16 -05:00
|
|
|
# false if the lease is already taken.
|
|
|
|
def try_obtain
|
2016-03-10 12:39:50 -05:00
|
|
|
# Performing a single SET is atomic
|
2017-07-10 23:35:47 -04:00
|
|
|
Gitlab::Redis::SharedState.with do |redis|
|
|
|
|
redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout) && @uuid
|
2016-03-16 13:10:03 -04:00
|
|
|
end
|
2016-03-10 04:41:16 -05:00
|
|
|
end
|
|
|
|
|
2017-06-23 01:36:19 -04:00
|
|
|
# Try to renew an existing lease. Return lease UUID on success,
|
|
|
|
# false if the lease is taken by a different UUID or inexistent.
|
|
|
|
def renew
|
2017-07-10 23:35:47 -04:00
|
|
|
Gitlab::Redis::SharedState.with do |redis|
|
|
|
|
result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_shared_state_key], argv: [@uuid, @timeout])
|
2017-06-23 01:36:19 -04:00
|
|
|
result == @uuid
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-25 10:01:24 -04:00
|
|
|
# Returns true if the key for this lease is set.
|
|
|
|
def exists?
|
2017-07-10 23:35:47 -04:00
|
|
|
Gitlab::Redis::SharedState.with do |redis|
|
|
|
|
redis.exists(@redis_shared_state_key)
|
2016-10-25 10:01:24 -04:00
|
|
|
end
|
|
|
|
end
|
2018-01-04 10:49:15 -05:00
|
|
|
|
|
|
|
# Returns the TTL of the Redis key.
|
|
|
|
#
|
|
|
|
# This method will return `nil` if no TTL could be obtained.
|
|
|
|
def ttl
|
|
|
|
Gitlab::Redis::SharedState.with do |redis|
|
|
|
|
ttl = redis.ttl(@redis_shared_state_key)
|
|
|
|
|
|
|
|
ttl if ttl.positive?
|
|
|
|
end
|
|
|
|
end
|
2016-03-10 04:41:16 -05:00
|
|
|
end
|
|
|
|
end
|