Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -6578,10 +6578,13 @@ synchronized IRubyObject load(ThreadContext context) {
// This method needs to be synchronized for removing Autoload
// from autoloadMap when it's loaded.
LoadService loadService = loadService(context);
if (loadService.featureAlreadyLoaded(path.asJavaString())) {
// Nothing to load here: the feature is loaded, or a direct require of it is in
// progress. Keeping the claim would make that require's definition of the
// constant look like this autoload's own and leave UNDEF in the constant table.
String[] loading = new String[1];
// If another thread is still requiring the file, autoloadRequire waits for it.
if (loadService.featureAlreadyLoaded(path.asJavaString(), loading) &&
(loading[0] == null || loadService.isRequiring(loading[0]))) {
// Nothing to load: the file is loaded, or this thread is requiring it. Clear ctx, or that
// require's definition of the constant would be kept as this autoload's value and never
// reach the constant table.
this.ctx = null;
} else if (loadService.autoloadRequire(path)) {
// Do not finish autoloading by cyclic autoload
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/org/jruby/runtime/load/LoadService.java
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,14 @@ protected LibrarySearcher.FoundLibrary searchForLoad(String file) {
return librarySearcher.findLibraryForLoad(file);
}

/**
* Whether this thread holds the require lock for the given key.
*/
public boolean isRequiring(String key) {
RequireLocks.RequireLock lock = requireLocks.pool.get(key);
return lock != null && lock.isHeldByCurrentThread();
}

public boolean featureAlreadyLoaded(String feature) {
return librarySearcher.featureAlreadyLoaded(feature, null);
}
Expand Down
30 changes: 30 additions & 0 deletions spec/ruby/core/module/autoload_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1157,4 +1157,34 @@ class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ
ModuleSpecs::Autoload::FromThread::D.foo
}.value.should == :foo
end

it "raises in every thread when two constants autoload the same file and it raises" do
dir = tmp("autoload_same_file_raising")
mkdir_p dir
File.write("#{dir}/autoload_same_file_raising.rb", "raise 'autoload failed'")
$LOAD_PATH.unshift dir
begin
# the second thread races the first one's require, so try a few times
10.times do
mod = Module.new
mod.autoload :First, "autoload_same_file_raising"
mod.autoload :Second, "autoload_same_file_raising"

threads = [:First, :Second].map { |name|
Thread.new {
begin
mod.const_get(name)
rescue Exception => e
e
end
}
}

threads.map { |t| t.value.message }.should == ["autoload failed", "autoload failed"]
end
ensure
$LOAD_PATH.delete dir
rm_r dir
end
end
end
Loading