Project

General

Profile

Bug #10892 ยป autoload_bug.rb

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

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

    
3
repeated_concurrent_autoload = '
4
prev_value = COUNTER.increment_and_get
5
eval <<-RUBY_EVAL
6
  module Mod#{prev_value}
7
    sleep(0.05)
8
    def self.foo
9
    end
10
  end
11
RUBY_EVAL
12
'
13

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

    
17
class CyclicBarrier
18
  def initialize(count)
19
    @count = count
20
    @state = 0
21
    @mutex = Mutex.new
22
    @cond  = ConditionVariable.new
23
  end
24

    
25
  def await
26
    @mutex.synchronize do
27
      @state += 1
28
      if @state >= @count
29
        @state = 0
30
        @cond.broadcast
31
        true
32
      else
33
        @cond.wait @mutex
34
        false
35
      end
36
    end
37
  end
38

    
39
  def enabled?
40
    @mutex.synchronize { @count != -1 }
41
  end
42

    
43
  def disable!
44
    @mutex.synchronize do
45
      @count = -1
46
      @cond.broadcast
47
    end
48
  end
49
end
50

    
51
class ThreadSafeCounter
52
  def initialize(value = 0)
53
    @value = value
54
    @mutex = Mutex.new
55
  end
56

    
57
  def get
58
    @mutex.synchronize { @value }
59
  end
60

    
61
  def increment_and_get
62
    @mutex.synchronize do
63
      prev_value = @value
64
      @value += 1
65
      prev_value
66
    end
67
  end
68
end
69

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

    
74
mod_names = []
75
mod_count.times do |i|
76
  mod_name = :"Mod#{i}"
77
  autoload mod_name, autoload_path
78
  mod_names << mod_name
79
end
80

    
81
barrier = CyclicBarrier.new thread_count
82
COUNTER = ThreadSafeCounter.new
83

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

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

    
94
      begin
95
        Object.const_get(mod_name).foo
96
      rescue NoMethodError
97
        barrier.disable!
98
        break false
99
      end
100
    end
101
  end
102
end
103

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

    
108
# check that the autoloaded file was evaled exactly once
109
p COUNTER.get
110
p mod_count