threads/autoload_under_load.rb
tests/_examples/threads/autoload_under_load.rb in the metorex repository.
# Several threads asking for the same autoloaded name at once. The one that
# gets there first loads the file, the rest wait for it, and every one of them
# reads a module that is finished rather than one part-way through being built.
#
# A method that runs threads still returns from itself afterwards, which is
# what the `return` below stands for.
class Counter
def initialize
@value = 0
@guard = Mutex.new
end
def get
@guard.synchronize { @value }
end
def increment_and_get
@guard.synchronize do
@value += 1
@value
end
end
end
class Gate
def initialize(count)
@count = count
@waiting = 0
@guard = Mutex.new
@gate = ConditionVariable.new
end
def await
@guard.synchronize do
@waiting += 1
if @waiting >= @count
@waiting = 0
@gate.broadcast
true
else
@gate.wait @guard
false
end
end
end
end
$counted_loads = Counter.new
file = File.expand_path("fixtures/counted_autoload.rb", __dir__)
named = file.sub(/\.rb\Z/, "")
names = [:Counted1, :Counted2, :Counted3]
names.each { |name| Object.autoload(name, named) }
def race(names, file, hands)
gate = Gate.new(hands)
threads = (1..hands).map do
Thread.new do
names.map do |name|
last_one_in = gate.await
$LOADED_FEATURES.delete(file) if last_one_in && $LOADED_FEATURES.include?(file)
gate.await
Object.const_get(name).ready
end
end
end
return threads.map { |thread| thread.value }
end
answers = race(names, file, 4)
p answers.all? { |answered| answered == [:ready, :ready, :ready] }
p $counted_loads.get
p names.map { |name| Object.const_get(name).ready }
Output:
true
3
[:ready, :ready, :ready]