summaryrefslogtreecommitdiff
path: root/lib/gitlab/reference_counter.rb
blob: f41e42b9e9c127d9e6190c72d52d5560c2f3084c (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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# frozen_string_literal: true

module Gitlab
  # Reference Counter
  #
  # A reference counter is used as a mechanism to identify when
  # a repository is being accessed by a writable operation.
  #
  # Maintenance operations would use this as a clue to when it should
  # execute significant changes in order to avoid disrupting running traffic
  class ReferenceCounter
    REFERENCE_EXPIRE_TIME = 600

    attr_reader :gl_repository, :key

    # Reference Counter instance
    #
    # @example
    #   Gitlab::ReferenceCounter.new('project-1')
    #
    # @see Gitlab::GlRepository::RepoType.identifier_for_repositorable
    # @param [String] gl_repository repository identifier
    def initialize(gl_repository)
      @gl_repository = gl_repository
      @key = "git-receive-pack-reference-counter:#{gl_repository}"
    end

    # Return the actual counter value
    #
    # @return [Integer] value
    def value
      Gitlab::Redis::SharedState.with do |redis|
        (redis.get(key) || 0).to_i
      end
    end

    # Increase the counter
    #
    # @return [Boolean] whether operation was a success
    def increase
      redis_cmd do |redis|
        redis.incr(key)
        redis.expire(key, REFERENCE_EXPIRE_TIME)
      end
    end

    # Decrease the counter
    #
    # @return [Boolean] whether operation was a success
    def decrease
      redis_cmd do |redis|
        current_value = redis.decr(key)
        if current_value < 0
          Gitlab::AppLogger.warn("Reference counter for #{gl_repository} decreased" \
            " when its value was less than 1. Resetting the counter.")
          redis.del(key)
        end
      end
    end

    # Reset the reference counter
    #
    # @private Used internally by SRE and debugging purpose
    # @return [Boolean] whether reset was a success
    def reset!
      redis_cmd do |redis|
        redis.del(key)
      end
    end

    # When the reference counter would expire
    #
    # @api private Used internally by SRE and debugging purpose
    # @return [Integer] Number in seconds until expiration or false if never
    def expires_in
      Gitlab::Redis::SharedState.with do |redis|
        redis.ttl(key)
      end
    end

    private

    def redis_cmd
      Gitlab::Redis::SharedState.with { |redis| yield(redis) }

      true
    rescue StandardError => e
      Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")

      false
    end
  end
end