summaryrefslogtreecommitdiff
path: root/config/initializers/forbid_sidekiq_in_transactions.rb
blob: bb190af60b563bacdf10cb4f37daf985611c9dd1 (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
module Sidekiq
  module Worker
    EnqueueFromTransactionError = Class.new(StandardError)

    def self.skipping_transaction_check(&block)
      previous_skip_transaction_check = self.skip_transaction_check
      Thread.current[:sidekiq_worker_skip_transaction_check] = true
      yield
    ensure
      Thread.current[:sidekiq_worker_skip_transaction_check] = previous_skip_transaction_check
    end

    def self.skip_transaction_check
      Thread.current[:sidekiq_worker_skip_transaction_check]
    end

    module ClassMethods
      module NoEnqueueingFromTransactions
        %i(perform_async perform_at perform_in).each do |name|
          define_method(name) do |*args|
            if !Sidekiq::Worker.skip_transaction_check && Gitlab::Database.inside_transaction?
              begin
                raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
                `#{self}.#{name}` cannot be called inside a transaction as this can lead to
                race conditions when the worker runs before the transaction is committed and
                tries to access a model that has not been saved yet.

                Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
                MSG
              rescue Sidekiq::Worker::EnqueueFromTransactionError => e
                ::Rails.logger.error(e.message) if ::Rails.env.production?
                Gitlab::Sentry.track_exception(e)
              end
            end

            super(*args)
          end
        end
      end

      prepend NoEnqueueingFromTransactions
    end
  end
end

module ActiveRecord
  class Base
    module SkipTransactionCheckAfterCommit
      def committed!(*)
        Sidekiq::Worker.skipping_transaction_check { super }
      end
    end

    prepend SkipTransactionCheckAfterCommit
  end
end