summaryrefslogtreecommitdiff
path: root/lib/gitlab/checks/matching_merge_request.rb
blob: e37cbc0442b587bce3c668cd07884c86f2d59c33 (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
# frozen_string_literal: true

module Gitlab
  module Checks
    class MatchingMergeRequest
      TOTAL_METRIC = :gitlab_merge_request_match_total
      STALE_METRIC = :gitlab_merge_request_match_stale_secondary

      def initialize(newrev, branch_name, project)
        @newrev = newrev
        @branch_name = branch_name
        @project = project
      end

      def match?
        if ::Gitlab::Database::LoadBalancing.enable?
          # When a user merges a merge request, the following sequence happens:
          #
          # 1. Sidekiq: MergeService runs and updates the merge request in a locked state.
          # 2. Gitaly: The UserMergeBranch RPC runs.
          # 3. Gitaly (gitaly-ruby): This RPC calls the pre-receive hook.
          # 4. Rails: This hook makes an API request to /api/v4/internal/allowed.
          # 5. Rails: This API check does a SQL query for locked merge
          #    requests with a matching SHA.
          #
          # Since steps 1 and 5 will happen on different database
          # sessions, replication lag could erroneously cause step 5 to
          # report no matching merge requests. To avoid this, we check
          # the write location to ensure the replica can make this query.
          track_session_metrics do
            ::Gitlab::Database::LoadBalancing::Sticking.select_valid_host(:project, @project.id)
          end
        end

        # rubocop: disable CodeReuse/ActiveRecord
        @project.merge_requests
          .with_state(:locked)
          .where(in_progress_merge_commit_sha: @newrev, target_branch: @branch_name)
          .exists?
        # rubocop: enable CodeReuse/ActiveRecord
      end

      private

      def track_session_metrics
        before = ::Gitlab::Database::LoadBalancing::Session.current.use_primary?

        yield

        after = ::Gitlab::Database::LoadBalancing::Session.current.use_primary?

        increment_attempt_count

        if !before && after
          increment_stale_secondary_count
        end
      end

      def increment_attempt_count
        total_counter.increment
      end

      def increment_stale_secondary_count
        stale_counter.increment
      end

      def total_counter
        @total_counter ||= ::Gitlab::Metrics.counter(TOTAL_METRIC, 'Total number of merge request match attempts')
      end

      def stale_counter
        @stale_counter ||= ::Gitlab::Metrics.counter(STALE_METRIC, 'Total number of merge request match attempts with lagging secondary')
      end
    end
  end
end