summaryrefslogtreecommitdiff
path: root/lib/gitlab/exclusive_lease.rb
blob: 2ef50286b1ddf6d3217eb0a714200a1823595d65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
  # 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.
  #
  class ExclusiveLease
    def initialize(key, timeout:)
      @key, @timeout = key, timeout
    end

    # Try to obtain the lease. Return true on success,
    # false if the lease is already taken.
    def try_obtain
      # Performing a single SET is atomic
      !!redis.set(redis_key, '1', nx: true, ex: @timeout)
    end

    private

    def redis
      # Maybe someday we want to use a connection pool...
      @redis ||= Redis.new(url: Gitlab::RedisConfig.url)
    end

    def redis_key
      "gitlab:exclusive_lease:#{@key}"
    end
  end
end