Class: Gitlab::ExclusiveLease

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/exclusive_lease.rb

Overview

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 servers. It is a 'cheap' alternative to using SQL queries and updates: you do not need to change the SQL schema to start using ExclusiveLease.

It is important to choose the timeout wisely. If the timeout is very high (1 hour) then the throughput of your operation gets very low (at most once an hour). If the timeout is lower than how long your operation may take then you cannot count on exclusivity. For example, if the timeout is 10 seconds and you do an operation which may take 20 seconds then two overlapping operations may hold a lease for the same key at the same time.

This class has no 'cancel' method. I originally decided against adding it because it would add complexity and a false sense of security. The complexity: instead of setting '1' we would have to set a UUID, and to delete it we would have to execute Lua on the Redis server to only delete the key if the value was our own UUID. Otherwise there is a chance that when you intend to cancel your lease you actually delete someone else's. The false sense of security: you cannot design your system to rely too much on the lease being cancelled after use because the calling (Ruby) process may crash or be killed. You cannot count on begin/ensure blocks to cancel a lease, because the 'ensure' does not always run. Think of 'kill -9' from the Unicorn master for instance.

If you find that leases are getting in your way, ask yourself: would it be enough to lower the lease timeout? Another thing that might be appropriate is to only use a lease for bulk/automated operations, and to ignore the lease when you get a single 'manual' user request (a button click).

Instance Method Summary collapse

Constructor Details

#initialize(key, timeout:) ⇒ ExclusiveLease

Returns a new instance of ExclusiveLease


38
39
40
# File 'lib/gitlab/exclusive_lease.rb', line 38

def initialize(key, timeout:)
  @key, @timeout = key, timeout
end

Instance Method Details

#try_obtainObject

Try to obtain the lease. Return true on success, false if the lease is already taken.


44
45
46
47
48
49
# File 'lib/gitlab/exclusive_lease.rb', line 44

def try_obtain
  # Performing a single SET is atomic
  Gitlab::Redis.with do |redis|
    !!redis.set(redis_key, '1', nx: true, ex: @timeout)
  end
end