summaryrefslogtreecommitdiff
path: root/lib/gitlab/exclusive_lease_helpers.rb
blob: 7cf0232fbf2de82cc6e1c2a0a7e57c4af0477ede (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
42
43
# frozen_string_literal: true

module Gitlab
  # This module provides helper methods which are intregrated with GitLab::ExclusiveLease
  module ExclusiveLeaseHelpers
    FailedToObtainLockError = Class.new(StandardError)

    ##
    # This helper method blocks a process/thread until the lease can be acquired, either due to
    # the lease TTL expiring, or due to the current holder explicitly releasing
    # their hold.
    #
    # If the lease cannot be obtained, raises `FailedToObtainLockError`.
    #
    # @param [String] key The lock the thread will try to acquire. Only one thread
    #                     in one process across all Rails instances can hold this named lock at any
    #                     one time.
    # @param [Float] ttl: The length of time the lock will be valid for. The lock
    #                    will be automatically be released after this time, so any work should be
    #                    completed within this time.
    # @param [Integer] retries: The maximum number of times we will re-attempt
    #                           to acquire the lock. The maximum number of attempts will be `retries + 1`:
    #                           one for the initial attempt, and then one for every re-try.
    # @param [Float|Proc] sleep_sec: Either a number of seconds to sleep, or
    #                                a proc that computes the sleep time given the number of preceding attempts
    #                               (from 1 to retries - 1)
    #
    # Note: It's basically discouraged to use this method in a webserver thread,
    #       because this ties up all thread related resources until all `retries` are consumed.
    #       This could potentially eat up all connection pools.
    def in_lock(key, ttl: 1.minute, retries: 10, sleep_sec: 0.01.seconds)
      raise ArgumentError, 'Key needs to be specified' unless key

      lease = SleepingLock.new(key, timeout: ttl, delay: sleep_sec)

      lease.obtain(1 + retries)

      yield(lease.retried?, lease)
    ensure
      lease&.cancel
    end
  end
end