forbid_sidekiq_in_transactions.rb 1.9 KB
Newer Older
1 2
module Sidekiq
  module Worker
3 4
    EnqueueFromTransactionError = Class.new(StandardError)

Douwe Maan's avatar
Douwe Maan committed
5 6 7 8 9 10 11 12 13 14
    mattr_accessor :skip_transaction_check
    self.skip_transaction_check = false

    def self.skipping_transaction_check(&block)
      skip_transaction_check = self.skip_transaction_check
      self.skip_transaction_check = true
      yield
    ensure
      self.skip_transaction_check = skip_transaction_check
    end
15

16
    module ClassMethods
17
      module NoEnqueueingFromTransactions
18 19
        %i(perform_async perform_at perform_in).each do |name|
          define_method(name) do |*args|
20
            if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
21 22
              begin
                raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
23 24 25
                `#{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.
26

27
                Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
28 29 30 31 32 33 34 35 36 37 38 39 40
                MSG
              rescue Sidekiq::Worker::EnqueueFromTransactionError => e
                if Rails.env.production?
                  Rails.logger.error(e.message)

                  if Gitlab::Sentry.enabled?
                    Gitlab::Sentry.context
                    Raven.capture_exception(e)
                  end
                else
                  raise
                end
              end
41
            end
42

43
            super(*args)
44 45 46 47
          end
        end
      end

48
      prepend NoEnqueueingFromTransactions
49 50 51
    end
  end
end
52 53 54

module ActiveRecord
  class Base
Douwe Maan's avatar
Douwe Maan committed
55
    module SkipTransactionCheckAfterCommit
56
      def committed!(*)
Douwe Maan's avatar
Douwe Maan committed
57
        Sidekiq::Worker.skipping_transaction_check { super }
58 59 60
      end
    end

Douwe Maan's avatar
Douwe Maan committed
61
    prepend SkipTransactionCheckAfterCommit
62 63
  end
end