From: "mame (Yusuke Endoh) via ruby-core" Date: 2025-06-05T10:39:55+00:00 Subject: [ruby-core:122449] [Ruby Misc#21154] Document or change Module#autoload? Issue #21154 has been updated by mame (Yusuke Endoh). > Think about this. You ship a gem like this: @fxn Thank you for the very interesting example. It became a topic of discussion for @akr, @naruse, and me for a total of 4 hours over two dev meetings. With a slight modification to your example, we can create a case that deadlocks. ```ruby # my_gem.rb module MyGem autoload :M, "./my_gem/m" end # my_gem/m.rb module MyGem module M autoload :X, "./my_gem/m/x.rb" sleep 1 X # (1) fires the autoload of my_gem/m/x.rb end end # my_gem/m/x.rb module MyGem sleep 1 module M # (2) fires the autoload of my_gem/m.rb X = 1 end end # main.rb require "./my_gem" Thread.new { MyGem::M } require "./my_gem/m/x" ``` ``` $ ruby main.rb /home/mame/work/ruby/my_gem/m/x.rb:4:in '': No live threads left. Deadlock? (fatal) 2 threads, 2 sleeps current:0x00005fda1937c900 main thread:0x00005fda19088ad0 *snip* ``` When `Kernel#require` attempts to require a file that is currently being required by another thread, it waits for the prior `require` to complete. In this example, `my_gem/m.rb` and `my_gem/m/x.rb` end up trying to require each other simultaneously, leading to a deadlock. --- At present, it's fair to say that Ruby doesn't well support to require/autoload mutually-dependent files from multiple entrypoints. In this specific case, to put it simply, users must not do `require "my_gem/m/x"`. We know this limitation is too strict. We explored ways to allow `require "my_gem/m/x"`: 1. Can we improve the Ruby interpreter to prevent deadlocks in the above case? 2. Can we document guidelines for what content is safe to write in files that are to be required (i.e., content that we can safely require from multiple entrypoints)? --- The simplest approach would be to introduce a single global lock for `require`, limiting it to at most one thread at a time. This would prevent deadlocks. However, if one thread requires a file that enters an infinite loop, `require` in other threads would be blocked indefinitely. We haven't come up with a better locking mechanism. --- It would be great if we could document clear guidelines like "if users write Ruby code this way, it's safe to require/autoload from multiple files," but we haven't found such clear guidelines. In the deadlock example above, the cause is accessing `X` within `my_gem/m.rb`, which triggers the autoload of `my_gem/m/x.rb`. Therefore, one might think that avoiding the immediate triggering of an autoload that one has set up would be sufficient. However, a deadlock still occurs if `my_gem/m.rb` directly requires `my_gem/m/x.rb`, like this: ```ruby # my_gem/m.rb sleep 1 require "./my_gem/m/x" # This directly requires the other file module MyGem module M end end ``` This `my_gem/m.rb` looks innocent to me, so what is wrong? It is a bit surprising that `module M` in `my_gem/m/x.rb` fires the autoload of `M`. One may think Ruby should stop doing that. However, it would not be rare to refer to `M` in `my_gem/m/x.rb`, such as, `M::Y`. What guideline can users follow to ensure that such deadlocks do not occur? Do you have any ideas? ---------------------------------------- Misc #21154: Document or change Module#autoload? https://bugs.ruby-lang.org/issues/21154#change-113630 * Author: fxn (Xavier Noria) * Status: Feedback * Assignee: mame (Yusuke Endoh) ---------------------------------------- The documentation of `Module#autoload?` says > Returns filename to be loaded if name is registered as autoload in the namespace of mod or one of its ancestors. Cool, but in the following snippet ```ruby autoload :Foo, 'foo' autoload?(:Foo) ``` the second line could evaluate to `nil`, and this does not seem to agree. I just registered an autoload, therefore (according to the documentation) I should get "foo" back in line 2. I'd like to ask for clarification from the Ruby team: 1. Is the documentation complete? Should that second line always return "foo"? 2. If the answer is no, which is the logic missing in the docs? Thank you! -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/