diff --git a/lib/zeitwerk/loader.rb b/lib/zeitwerk/loader.rb index 6554aff..1fbdbda 100644 --- a/lib/zeitwerk/loader.rb +++ b/lib/zeitwerk/loader.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "monitor" require "set" module Zeitwerk @@ -91,9 +92,9 @@ class Loader attr_reader :mutex private :mutex - # @sig Mutex - attr_reader :mutex2 - private :mutex2 + # @sig Monitor + attr_reader :dirs_autoload_monitor + private :dirs_autoload_monitor def initialize super @@ -103,11 +104,12 @@ def initialize @to_unload = {} @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] } @shadowed_files = Set.new - @mutex = Mutex.new - @mutex2 = Mutex.new @setup = false @eager_loaded = false + @mutex = Mutex.new + @dirs_autoload_monitor = Monitor.new + Registry.register_loader(self) end diff --git a/lib/zeitwerk/loader/callbacks.rb b/lib/zeitwerk/loader/callbacks.rb index fc91d96..17b97ce 100644 --- a/lib/zeitwerk/loader/callbacks.rb +++ b/lib/zeitwerk/loader/callbacks.rb @@ -44,7 +44,7 @@ def on_dir_autoloaded(dir) # That not only would reassign the constant (undesirable per se) but, worse, # the module object created by t2 wouldn't have any of the autoloads for its # children, since t1 would have correctly deleted its namespace_dirs entry. - mutex2.synchronize do + dirs_autoload_monitor.synchronize do if cref = autoloads.delete(dir) autovivified_module = cref[0].const_set(cref[1], Module.new) cpath = autovivified_module.name diff --git a/test/lib/zeitwerk/test_on_load.rb b/test/lib/zeitwerk/test_on_load.rb index 3b84ca6..c05d09b 100644 --- a/test/lib/zeitwerk/test_on_load.rb +++ b/test/lib/zeitwerk/test_on_load.rb @@ -191,4 +191,15 @@ class TestOnLoad < LoaderTest assert loader.send(:on_load_callbacks).empty? end end + + test "on_load is reentrant for implicit namespaces" do + files = [["m/x.rb", "M::X = true"], ["n/x.rb", "N::X = true"]] + with_setup(files) do + loaded = false + loader.on_load("M") { loaded = N } + + assert M + assert loaded + end + end end