Project

General

Profile

Actions

Bug #21830

closed

Ruby::Box and Kernel#require

Bug #21830: Ruby::Box and Kernel#require

Added by fxn (Xavier Noria) 8 months ago. Updated 6 days ago.


Description

If you enable Ruby::Box, decorations to Kernel#require do not work.

For example, given foo.rb:

module M
  def require(_path)
    puts 'decorated'
    super
  end
end

Kernel.prepend(M)

p require 'tsort'

the decoration works normally:

% ruby -v foo.rb
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25]
decorated
true

but it does not with the feature enabled:

% RUBY_BOX=1 ruby -v foo.rb
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25]
ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/4.0/Ruby/Box.html for known issues, etc.
true

With this, Zeitwerk cannot work because it totally depends on a decoration of Kernel#require (done here).

Bootsnap also depends on the ability of decorating Kernel#require (done here).

On one hand, these gems are walking a fine line in the border of the public interface of Ruby. On the other hand, it is a goal of boxes to be transparent to the code being loaded under them.

@tagomoris (Satoshi Tagomori) (Satoshi Tagomori) what should be the way to go in your opinion?

/cc @byroot (Jean Boussier) (Jean Boussier)


Related issues 1 (1 open0 closed)

Related to Ruby - Misc #22275: Ruby::Box support plan for RubyGems and BundlerOpenActions

Updated by fxn (Xavier Noria) 8 months ago Actions #1

  • Description updated (diff)

Updated by fxn (Xavier Noria) 8 months ago ยท Edited Actions #2 [ruby-core:124463]

Just in case it matters, let me also add that it is assumed that Module#autoload invokes Kernel#require as introduced in cd465d5.

Zeitwerk relies on this too, because the main intercepted require calls are coming from autoloads set by autoloaders.

Updated by tagomoris (Satoshi Tagomori) 8 months ago Actions #3 [ruby-core:124473]

  • Assignee set to tagomoris (Satoshi Tagomori)

Updated by tagomoris (Satoshi Tagomori) 7 months ago Actions #4 [ruby-core:124827]

I'll work on #21881 to solve this problem.

Updated by hsbt (Hiroshi SHIBATA) 10 days ago Actions #5

The direct Kernel#require decoration now works on master (after #21881), but the autoload path is still broken: an autoload fired under RUBY_BOX=1 bypasses the decorated require entirely.

module M
  def require(path)
    if path == "/nonexistent/virtual"
      Object.const_set(:A, Module.new)
      return true
    end
    super
  end
end
Kernel.prepend(M)
Object.autoload(:A, "/nonexistent/virtual")
A # defined without RUBY_BOX, LoadError with RUBY_BOX=1

Root cause: autoload_feature_require in variable.c replaces the receiver with the registered box object when boxes are enabled, so the call dispatches to Ruby::Box#require, which goes straight to rb_require_string and skips everything prepended to Kernel in that box. This breaks Zeitwerk's implicit namespaces: with RUBY_BOX=1, a plain Rails 8.1 app boots but the first request fails with LoadError: cannot load such file -- .../turbo-rails-2.0.23/app/channels/turbo/streams.

I opened https://github.com/ruby/ruby/pull/18534 to fix this. It calls require on the registered box's top self in a frame running in that box, so the box's decorated require is dispatched and the feature is still loaded into the registering box. With it, RUBY_BOX=1 rails server serves requests.

Updated by fxn (Xavier Noria) 9 days ago 1Actions #6 [ruby-core:126516]

Thanks @hsbt (Hiroshi SHIBATA).

For the archives, I'd like to add something to

This breaks Zeitwerk's implicit namespaces

The main idea in Zeitwerk is:

  1. Scan the filesystem
  2. Set autoloads accordingly
  3. React to constants being autoloded

The Kernel#require decoration takes care of (3). In that decoration, we update internal state, we trigger user on load callbacks, etc.

So, while it is correct that implicit namespaces won't work, the implications are more dramatic: Zeitwerk in general won't work, callbacks won't fire, reloading won't know what to reload, etc. If the decoration is not in place the gem is generally broken.

Updated by hsbt (Hiroshi SHIBATA) 9 days ago Actions #7

  • Related to Misc #22275: Ruby::Box support plan for RubyGems and Bundler added

Updated by hsbt (Hiroshi SHIBATA) 6 days ago Actions #8 [ruby-core:126548]

  • Status changed from Open to Closed

@fxn (Xavier Noria) Thank you for your following up. I merged https://github.com/ruby/ruby/pull/18534, so rails s is now working with RUBY_BOX=1.

I'm traking the support status of RubyGems and Ruby::Box at https://bugs.ruby-lang.org/issues/22275

Actions

Also available in: PDF Atom