Bug #21324
openNamespace loads RubyGems in root Namespace but it should not
Description
RubyGems has tons of mutable state, isn't core library and isn't "builtin classes" either, so it should not be in root Namespace, but it is currently:
$ RUBY_NAMESPACE=1 ruby -ve 'ns = Namespace.new; p ns::Gem.equal?(Gem)'
ruby 3.5.0dev (2025-05-10T07:50:29Z namespace-on-read-.. bd4f57f96b) +PRISM [x86_64-linux]
ruby: warning: Namespace is experimental, and the behavior may change in the future!
See doc/namespace.md for know issues, etc.
true
A concrete example of what breaks most likely due to this:
$ gem i delegate:0.3.1
$ RUBY_NAMESPACE=1 ruby -ve 'require "delegate"; p Delegator::VERSION; ns = Namespace.new; File.write "ns.rb", "gem %{delegate}, %{0.3.1}; require :delegate.to_s; p Delegator::VERSION"; ns.require "./ns"'
ruby 3.5.0dev (2025-05-10T07:50:29Z namespace-on-read-.. bd4f57f96b) +PRISM [x86_64-linux]
ruby: warning: Namespace is experimental, and the behavior may change in the future!
See doc/namespace.md for know issues, etc.
"0.4.0"
/home/eregon/prefix/ruby-master/lib/ruby/3.5.0+0/rubygems/specification.rb:2232:in 'Gem::Specification#check_version_conflict': can't activate delegate-0.3.1, already activated delegate-0.4.0 (Gem::LoadError)
from /home/eregon/prefix/ruby-master/lib/ruby/3.5.0+0/rubygems/specification.rb:1383:in 'Gem::Specification#activate'
from /home/eregon/prefix/ruby-master/lib/ruby/3.5.0+0/rubygems/core_ext/kernel_gem.rb:62:in 'block in Kernel#gem'
from /home/eregon/prefix/ruby-master/lib/ruby/3.5.0+0/rubygems/core_ext/kernel_gem.rb:62:in 'Thread::Mutex#synchronize'
from /home/eregon/prefix/ruby-master/lib/ruby/3.5.0+0/rubygems/core_ext/kernel_gem.rb:62:in 'Kernel#gem'
from /home/eregon/ns.rb:1:in '<top (required)>'
from -e:1:in 'Namespace#require'
from -e:1:in '<main>'
i.e. ns
sees delegate-0.4.0 was loaded in main namespace but it shouldn't.
Previously mentioned in https://bugs.ruby-lang.org/issues/21311#note-36
Updated by Eregon (Benoit Daloze) 4 days ago
- Related to Bug #21323: irb fails to start with Namespace added
Updated by mame (Yusuke Endoh) 4 days ago
- Assignee set to prism
Updated by mame (Yusuke Endoh) 4 days ago
- Assignee deleted (
prism)
Updated by jas (Jasveen Sandral) 1 day ago
I've identified the issue and prepared a fix.
The problem is that RubyGems is being loaded into the root namespace during Ruby's boot process, and then all user namespaces inherit this same Gem constant through the Copy-on-Write mechanism. This causes all namespaces to share the same RubyGems environment and state, leading to version conflicts.
The fix involves:
-
In
rb_initialize_main_namespace()
:- Detecting if the Gem constant exists in Object
- Removing it explicitly with rb_const_remove()
- Loading a fresh RubyGems environment
-
In
namespace_initialize()
(for Namespace.new):- Getting the namespace's own Object constant
- Checking and removing any inherited Gem constant
- Loading a clean RubyGems environment
This ensures each namespace (main and optional) gets its own isolated RubyGems state, preventing version conflicts when loading gems across namespaces.
I've created a PR with these changes: https://github.com/ruby/ruby/pull/13313
Updated by Eregon (Benoit Daloze) 1 day ago
Loading RubyGems creates more constants than just Gem
:
$ ruby -v --disable-gems -e 'a=Object.constants; require "rubygems"; b=Object.constants; p b-a'
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux]
[:Monitor, :MonitorMixin, :Gem, :CROSS_COMPILING, :RbConfig, :RUBYGEMS_ACTIVATION_MONITOR]
It seems very messy to try to remove that from the root namespace.
The clean solution seems obvious: gem_prelude.rb
shouldn't be loaded in the root namespace, but only in user namespaces.