Updating to recent RubySpec seems to show a bug under concurrent autoload.
I attach the extracted logic to reproduce.
At me, the script ends with either, in most cases,
autoload_bug.rb:105:in `value': No live threads left. Deadlock? (fatal)
from autoload_bug.rb:105:in `map'
from autoload_bug.rb:105:in `<main>'
Or:
autoload_bug.rb:95:in `const_get': uninitialized constant Mod1 (NameError)
from autoload_bug.rb:95:in `block (3 levels) in <main>'
from autoload_bug.rb:86:in `each'
from autoload_bug.rb:86:in `block (2 levels) in <main>'
Which both seem incorrect behavior.
All versions from 2.0 seem affected, and 1.9.3 behavior seems wrong but differently.
Could someone confirm this is a bug?
Is it likely to be fixed?
That broken rubyspec was written by me. The problem lies with repeatedly autoloading 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:
defwith_autoload_file(const_name,file_name='foo.rb')mangled_file_name=file_name.sub(/\.rb\Z/,'____temp____autoload.rb')# avoid accidentally overwriting any filesFile.write(mangled_file_name,"sleep 1; module #{const_name}; end")autoloadconst_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))yieldensureFile.delete(mangled_file_name)endfoo_ready=bar_waiting=bar_ready=falset=Thread.newdoThread.passuntilfoo_readyFoobar_waiting=trueThread.passuntilbar_readyBarendwith_autoload_file('Foo')dofoo_ready=trueFooendThread.passuntilbar_waitingwith_autoload_file('Bar')dobar_ready=trueBarendt.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')doBarbar_ready=trueend
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:
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.
Fwiw, I mentioned in [ruby-core:70359] that I tried it for [Bug #11384]
without success, but Redmine + list integration was broken at the
time.
Ah indeed I missed that, thanks.
Did you try for this issue in particular?
Ah, yes, the repro script in [ruby-core:70197] does get fixed on my
machine with the patch. I don't understand this code enough to
know if it breaks anything else, or if #11384 is a different bug
or a different manifestation of the same bug.