blob: e5e17672c4ed7bc0aeb39633920c55faadd7dd45 (
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
|
# frozen_string_literal: true
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 && ApplicationRecord.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
Gitlab::AppLogger.error(e.message) if ::Rails.env.production?
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
end
super(*args)
end
end
end
prepend NoEnqueueingFromTransactions
end
end
end
module ActiveRecord
class Base
module SkipTransactionCheckAfterCommit
def committed!(*args, **kwargs)
Sidekiq::Worker.skipping_transaction_check { super }
end
end
prepend SkipTransactionCheckAfterCommit
end
end
|