1
0
Fork 0
mirror of https://github.com/mperham/sidekiq.git synced 2022-11-09 13:52:34 -05:00

Synchronize code that reads/undefines methods in class attrs (#3997)

Fixes #3659 and #2302

The problem is that when two classes are accessed from separate threads they can
race to undef/read these methods.
This commit is contained in:
Matt Books 2018-10-18 08:27:45 -07:00 committed by Mike Perham
parent b5cad42e81
commit 64dfc280de
2 changed files with 35 additions and 3 deletions

View file

@ -66,6 +66,7 @@ module Sidekiq
end
module ClassMethods
ACCESSOR_MUTEX = Mutex.new
def delay(*args)
raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
@ -148,10 +149,18 @@ module Sidekiq
instance_writer = true
attrs.each do |name|
synchronized_getter = "__synchronized_#{name}"
singleton_class.instance_eval do
undef_method(name) if method_defined?(name) || private_method_defined?(name)
end
define_singleton_method(name) { nil }
define_singleton_method(synchronized_getter) { nil }
singleton_class.class_eval do
private(synchronized_getter)
end
define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
ivar = "@#{name}"
@ -161,8 +170,10 @@ module Sidekiq
end
define_singleton_method("#{name}=") do |val|
singleton_class.class_eval do
undef_method(name) if method_defined?(name) || private_method_defined?(name)
define_method(name) { val }
ACCESSOR_MUTEX.synchronize do
undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
define_method(synchronized_getter) { val }
end
end
if singleton_class?

View file

@ -287,4 +287,25 @@ class TestClient < Sidekiq::Test
assert_equal 12, job['retry']
end
end
describe 'class attribute race conditions' do
new_class = -> {
Class.new do
class_eval('include Sidekiq::Worker')
define_method(:foo) { get_sidekiq_options }
end
}
it 'does not explode when new initializing classes from multiple threads' do
10000.times do
klass = new_class.call
t1 = Thread.new { klass.sidekiq_options({}) }
t2 = Thread.new { klass.sidekiq_options({}) }
t1.join
t2.join
end
end
end
end