Welcome to mirror list, hosted at ThFree Co, Russian Federation.

worker_data_consistency_with_deduplication.rb « sidekiq_load_balancing « cop « rubocop - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: e8b4b513a23aa142629fed654080309ba9ccda9c (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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# frozen_string_literal: true

require_relative '../../code_reuse_helpers'

module RuboCop
  module Cop
    module SidekiqLoadBalancing
      # This cop checks for including_scheduled: true option in idempotent Sidekiq workers that utilize load balancing capabilities.
      #
      # @example
      #
      # # bad
      # class BadWorker
      #   include ApplicationWorker
      #
      #   data_consistency :delayed
      #   idempotent!
      #
      #   def perform
      #   end
      # end
      #
      # # bad
      # class BadWorker
      #   include ApplicationWorker
      #
      #   data_consistency :delayed
      #
      #   deduplicate :until_executing
      #   idempotent!
      #
      #   def perform
      #   end
      # end
      #
      # # good
      # class GoodWorker
      #   include ApplicationWorker
      #
      #   data_consistency :delayed
      #
      #   deduplicate :until_executing, including_scheduled: true
      #   idempotent!
      #
      #   def perform
      #   end
      # end
      #
      class WorkerDataConsistencyWithDeduplication < RuboCop::Cop::Base
        include CodeReuseHelpers
        extend AutoCorrector

        HELP_LINK = 'https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#scheduling-jobs-in-the-future'
        REPLACEMENT = ', including_scheduled: true'
        DEFAULT_STRATEGY = ':until_executing'

        MSG = <<~MSG
          Workers that declare either `:sticky` or `:delayed` data consistency become eligible for database load-balancing.
          In both cases, jobs are enqueued with a short delay.

          If you do want to deduplicate jobs that utilize load-balancing, you need to specify including_scheduled: true
          argument when defining deduplication strategy.

          See #{HELP_LINK} for a more detailed explanation of these settings.
        MSG

        def_node_search :application_worker?, <<~PATTERN
          `(send nil? :include (const nil? :ApplicationWorker))
        PATTERN

        def_node_search :idempotent_worker?, <<~PATTERN
          `(send nil? :idempotent!)
        PATTERN

        def_node_search :data_consistency_defined?, <<~PATTERN
          `(send nil? :data_consistency (sym {:sticky :delayed }))
        PATTERN

        def_node_matcher :including_scheduled?, <<~PATTERN
          `(hash <(pair (sym :including_scheduled) (%1)) ...>)
        PATTERN

        def_node_matcher :deduplicate_strategy?, <<~PATTERN
          `(send nil? :deduplicate (sym $_) $(...)?)
        PATTERN

        def on_class(node)
          return unless in_worker?(node)
          return unless application_worker?(node)
          return unless idempotent_worker?(node)
          return unless data_consistency_defined?(node)

          @strategy, options = deduplicate_strategy?(node)
          including_scheduled = false
          if options
            @deduplicate_options = options[0]
            including_scheduled = including_scheduled?(@deduplicate_options, :true) # rubocop:disable Lint/BooleanSymbol
          end

          @offense = !(including_scheduled || @strategy == :none)
        end

        def on_send(node)
          return unless offense

          if node.children[1] == :deduplicate
            add_offense(node.loc.expression) do |corrector|
              autocorrect_deduplicate_strategy(node, corrector)
            end
          elsif node.children[1] == :idempotent! && !strategy
            add_offense(node.loc.expression) do |corrector|
              autocorrect_missing_deduplicate_strategy(node, corrector)
            end
          end
        end

        private

        attr_reader :offense, :deduplicate_options, :strategy

        def autocorrect_deduplicate_with_options(corrector)
          if including_scheduled?(deduplicate_options, :false) # rubocop:disable Lint/BooleanSymbol
            replacement = deduplicate_options.source.sub("including_scheduled: false", "including_scheduled: true")
            corrector.replace(deduplicate_options.loc.expression, replacement)
          else
            corrector.insert_after(deduplicate_options.loc.expression, REPLACEMENT)
          end
        end

        def autocorrect_deduplicate_without_options(node, corrector)
          corrector.insert_after(node.loc.expression, REPLACEMENT)
        end

        def autocorrect_missing_deduplicate_strategy(node, corrector)
          indent_found = node.source_range.source_line =~ /^( +)/
          # Get indentation size
          whitespaces = Regexp.last_match(1).size if indent_found
          replacement = "deduplicate #{DEFAULT_STRATEGY}#{REPLACEMENT}\n"
          # Add indentation in the end since we are inserting a whole line before idempotent!
          replacement += ' ' * whitespaces.to_i
          corrector.insert_before(node.source_range, replacement)
        end

        def autocorrect_deduplicate_strategy(node, corrector)
          if deduplicate_options
            autocorrect_deduplicate_with_options(corrector)
          else
            autocorrect_deduplicate_without_options(node, corrector)
          end
        end
      end
    end
  end
end