That broken rubyspec was written by me. The problem lies with repeatedly autoload
ing the same .rb
file, since this should be impossible, the spec manually deletes the loaded path from $LOADED_FEATURES
and then re-declares the autoload
, this is currently broken on MRI.
Here's a much smaller repro script:
def with_autoload_file(const_name, file_name = 'foo.rb')
mangled_file_name = file_name.sub(/\.rb\Z/, '____temp____autoload.rb') # avoid accidentally overwriting any files
File.write(mangled_file_name, "sleep 1; module #{const_name}; end")
autoload const_name, File.expand_path(mangled_file_name.sub(/\.rb\Z/, ''))
$LOADED_FEATURES.delete(File.expand_path(mangled_file_name)) if $LOADED_FEATURES.include?(File.expand_path(mangled_file_name))
yield
ensure
File.delete(mangled_file_name)
end
foo_ready = bar_waiting = bar_ready = false
t = Thread.new do
Thread.pass until foo_ready
Foo
bar_waiting = true
Thread.pass until bar_ready
Bar
end
with_autoload_file('Foo') do
foo_ready = true
Foo
end
Thread.pass until bar_waiting
with_autoload_file('Bar') do
bar_ready = true
Bar
end
t.join
Running this results in an "uninitialized constant Bar
" exception from the non-main thread.
If the last block is rearranged like this:
with_autoload_file('Bar') do
Bar
bar_ready = true
end
the script deadlocks (main thread deadlocks, while secondary thread t
busy spins in Thread.pass until bar_ready
).
If the last autoload
block uses a different .rb
file, everything works fine:
with_autoload_file('Bar', 'bar.rb') do
Bar
bar_ready = true
end
I think I've tracked the issue to an incorrectly locked load_lock
's thread_shield
: when rb_thread_shield_wait()
returns Qfalse
the failed thread creates a new thread_shield
via rb_thread_shield_new()
, however because rb_thread_shield_new()
automatically locks the newly created shield and the branch does not return a successful ftptr
, the newly installed shield is then never unlocked.
The attached patch seems to fix the issue for me.