Project

General

Profile

Bug #10892 » autoload_bug.rb

Eregon (Benoit Daloze), 02/23/2015 12:34 PM

 
# Module#autoload (concurrently) blocks others threads while doing an autoload ERROR

repeated_concurrent_autoload = '
prev_value = COUNTER.increment_and_get
eval <<-RUBY_EVAL
module Mod#{prev_value}
sleep(0.05)
def self.foo
end
end
RUBY_EVAL
'

file_path = File.expand_path "repeated_concurrent_autoload.rb"
File.write(file_path, repeated_concurrent_autoload)

class CyclicBarrier
def initialize(count)
@count = count
@state = 0
@mutex = Mutex.new
@cond = ConditionVariable.new
end

def await
@mutex.synchronize do
@state += 1
if @state >= @count
@state = 0
@cond.broadcast
true
else
@cond.wait @mutex
false
end
end
end

def enabled?
@mutex.synchronize { @count != -1 }
end

def disable!
@mutex.synchronize do
@count = -1
@cond.broadcast
end
end
end

class ThreadSafeCounter
def initialize(value = 0)
@value = value
@mutex = Mutex.new
end

def get
@mutex.synchronize { @value }
end

def increment_and_get
@mutex.synchronize do
prev_value = @value
@value += 1
prev_value
end
end
end

autoload_path = file_path.sub(/\.rb\Z/, '')
mod_count = 30
thread_count = 16

mod_names = []
mod_count.times do |i|
mod_name = :"Mod#{i}"
autoload mod_name, autoload_path
mod_names << mod_name
end

barrier = CyclicBarrier.new thread_count
COUNTER = ThreadSafeCounter.new

threads = (1..thread_count).map do
Thread.new do
mod_names.each do |mod_name|
break false unless barrier.enabled?

was_last_one_in = barrier.await # wait for all threads to finish the iteration
# clean up so we can autoload the same file again
$LOADED_FEATURES.delete(file_path) if was_last_one_in && $LOADED_FEATURES.include?(file_path)
barrier.await # get ready for race

begin
Object.const_get(mod_name).foo
rescue NoMethodError
barrier.disable!
break false
end
end
end
end

# check that no thread got a NoMethodError because of partially loaded module
p threads.map(&:value)
p threads.all?(&:value)

# check that the autoloaded file was evaled exactly once
p COUNTER.get
p mod_count
(1-1/2)