diff options
Diffstat (limited to 'lib/gitlab/exclusive_lease.rb')
-rw-r--r-- | lib/gitlab/exclusive_lease.rb | 95 |
1 files changed, 8 insertions, 87 deletions
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb index 8679f17eb9b..e887e455792 100644 --- a/lib/gitlab/exclusive_lease.rb +++ b/lib/gitlab/exclusive_lease.rb @@ -12,8 +12,6 @@ module Gitlab # ExclusiveLease. # class ExclusiveLease - include Gitlab::Utils::StrongMemoize - PREFIX = 'gitlab:exclusive_lease' NoKey = Class.new(ArgumentError) @@ -33,7 +31,7 @@ module Gitlab EOS def self.get_uuid(key) - with_read_redis do |redis| + Gitlab::Redis::ClusterSharedState.with do |redis| redis.get(redis_shared_state_key(key)) || false end end @@ -63,7 +61,7 @@ module Gitlab def self.cancel(key, uuid) return unless key.present? - with_write_redis do |redis| + Gitlab::Redis::ClusterSharedState.with do |redis| redis.eval(LUA_CANCEL_SCRIPT, keys: [ensure_prefixed_key(key)], argv: [uuid]) end end @@ -81,12 +79,6 @@ module Gitlab # Removes any existing exclusive_lease from redis # Don't run this in a live system without making sure no one is using the leases def self.reset_all!(scope = '*') - Gitlab::Redis::SharedState.with do |redis| - redis.scan_each(match: redis_shared_state_key(scope)).each do |key| - redis.del(key) - end - end - Gitlab::Redis::ClusterSharedState.with do |redis| redis.scan_each(match: redis_shared_state_key(scope)).each do |key| redis.del(key) @@ -94,15 +86,6 @@ module Gitlab end end - def self.use_cluster_shared_state? - Gitlab::SafeRequestStore[:use_cluster_shared_state] ||= - Feature.enabled?(:use_cluster_shared_state_for_exclusive_lease) - end - - def self.use_double_lock? - Gitlab::SafeRequestStore[:use_double_lock] ||= Feature.enabled?(:enable_exclusive_lease_double_lock_rw) - end - def initialize(key, uuid: nil, timeout:) @redis_shared_state_key = self.class.redis_shared_state_key(key) @timeout = timeout @@ -112,23 +95,10 @@ module Gitlab # Try to obtain the lease. Return lease UUID on success, # false if the lease is already taken. def try_obtain - return try_obtain_with_new_lock if self.class.use_cluster_shared_state? - # Performing a single SET is atomic - obtained = set_lease(Gitlab::Redis::SharedState) && @uuid - - # traffic to new store is minimal since only the first lock holder can run SETNX in ClusterSharedState - return false unless obtained - return obtained unless self.class.use_double_lock? - return obtained if same_store # 2nd setnx will surely fail if store are the same - - second_lock_obtained = set_lease(Gitlab::Redis::ClusterSharedState) && @uuid - - # cancel is safe since it deletes key only if value matches uuid - # i.e. it will not delete the held lock on ClusterSharedState - cancel unless second_lock_obtained - - second_lock_obtained + Gitlab::Redis::ClusterSharedState.with do |redis| + redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout) && @uuid + end end # This lease is waiting to obtain @@ -139,7 +109,7 @@ module Gitlab # 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 - self.class.with_write_redis do |redis| + Gitlab::Redis::ClusterSharedState.with do |redis| result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_shared_state_key], argv: [@uuid, @timeout]) result == @uuid end @@ -147,7 +117,7 @@ module Gitlab # Returns true if the key for this lease is set. def exists? - self.class.with_read_redis do |redis| + Gitlab::Redis::ClusterSharedState.with do |redis| redis.exists?(@redis_shared_state_key) # rubocop:disable CodeReuse/ActiveRecord end end @@ -156,66 +126,17 @@ module Gitlab # # This method will return `nil` if no TTL could be obtained. def ttl - self.class.with_read_redis do |redis| + Gitlab::Redis::ClusterSharedState.with do |redis| ttl = redis.ttl(@redis_shared_state_key) ttl if ttl > 0 end end - # rubocop:disable CodeReuse/ActiveRecord - def self.with_write_redis(&blk) - if use_cluster_shared_state? - result = Gitlab::Redis::ClusterSharedState.with(&blk) - Gitlab::Redis::SharedState.with(&blk) - - result - elsif use_double_lock? - result = Gitlab::Redis::SharedState.with(&blk) - Gitlab::Redis::ClusterSharedState.with(&blk) - - result - else - Gitlab::Redis::SharedState.with(&blk) - end - end - - def self.with_read_redis(&blk) - if use_cluster_shared_state? - Gitlab::Redis::ClusterSharedState.with(&blk) - elsif use_double_lock? - Gitlab::Redis::SharedState.with(&blk) || Gitlab::Redis::ClusterSharedState.with(&blk) - else - Gitlab::Redis::SharedState.with(&blk) - end - end - # rubocop:enable CodeReuse/ActiveRecord - # Gives up this lease, allowing it to be obtained by others. def cancel self.class.cancel(@redis_shared_state_key, @uuid) end - - private - - def set_lease(redis_class) - redis_class.with do |redis| - redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout) - end - end - - def try_obtain_with_new_lock - # checks shared-state to avoid 2 versions of the application acquiring 1 lock - # wait for held lock to expire or yielded in case any process on old version is running - return false if Gitlab::Redis::SharedState.with { |c| c.exists?(@redis_shared_state_key) } # rubocop:disable CodeReuse/ActiveRecord - - set_lease(Gitlab::Redis::ClusterSharedState) && @uuid - end - - def same_store - Gitlab::Redis::ClusterSharedState.with(&:id) == Gitlab::Redis::SharedState.with(&:id) # rubocop:disable CodeReuse/ActiveRecord - end - strong_memoize_attr :same_store end end |