Project

General

Profile

Actions

Misc #22275

open

Ruby::Box support plan for RubyGems and Bundler

Misc #22275: Ruby::Box support plan for RubyGems and Bundler

Added by hsbt (Hiroshi SHIBATA) 24 days ago. Updated 3 days ago.

Status:
Open
Assignee:
-
[ruby-core:126658]

Description

This is a tracking issue for making RubyGems and Bundler work with Ruby::Box, so that reviewers can see the whole picture and what to look at next in one place.

Goal

The goal is to realize Feature #13847 with Ruby::Box, which means loading a specific version of a gem, including default gems and C extensions, isolated from the copy that RubyGems and Bundler themselves use. Vendoring with namespace rewriting has been our workaround for pure-Ruby gems for years. Box removes that limitation.

As a further step, we want RubyGems and Bundler to activate different gem versions per box. That enables the use cases already proposed around Box, such as gradual dependency upgrades, plugin systems with conflicting dependencies, and multiple applications in one process.

First milestone

Before designing such APIs, Box needs to leave experimental status. Our first milestone is that Rails and RubyGems/Bundler themselves work under RUBY_BOX=1, and that the ruby/rubygems repository runs its test suites with Box enabled continuously so we do not regress.


Current status

ruby/rubygems now runs both suites against a ruby-core master build with and without RUBY_BOX=1 on every pull request and push (ruby/rubygems#9826), and all four jobs pass. That covers the continuous part of the first milestone for ruby/rubygems.

With the stdio, $? and defined? fixes merged, ruby/rubygems#9892 removed 28 pending marks from the rubygems suite and 3 skips from the bundler suite. The rubygems suite now runs 3619 tests with 0 failures in both modes, with 10 pendings under Box and 8 without. The two box-only pendings wait on $VERBOSE, and the remaining bundler skip waits on -r, both below.

ruby/ruby's own make check passes under RUBY_BOX=1 on ruby/ruby#18705, which collects every open fix below and adds a CI lane for it. The branch is for measuring the integrated state, not for merging as it is. Decisions 3 to 7 came out of that work.

Critical problems

  • ruby/ruby#18579 (Bug #22282) is the last fix the rubygems suite waits on: under Box, $VERBOSE = nil does not reach the interpreter. @tagomoris (Satoshi Tagomori) asked on the pull request whether $VERBOSE can stay per box instead, which is decision 3.
  • Bug #22295 (decision 2) has no fix yet. The bundler install specs skip it, and the cargo builder test works around it by requiring inside -e.

Decisions needed

These block the rest and need a direction rather than more review.

  1. Which box a builtin written in Ruby resolves its calls in. It is the master box by default, which matches not doing local rebinding (Bug #21362#note-3). ruby/ruby#18544 settled Marshal.load by opting it into the caller's box with Primitive.attr! :caller_user_box. What remains is which other builtins opt in the same way, such as Ractor.new (ruby/ruby#18821), Kernel#clone, Kernel#warn and prelude (Bug #22332), and whether core-class stubs should reach builtins (Misc #22296). Opting every builtin in by default brings the inline cache leak of Bug #21362 back, because all boxes share a builtin's call sites. On the ruby/ruby#18705 branch, which tries that, redefining Integer#succ in one box changes 10.times.to_a in the main box.
  2. Whether command-line -r activates gems, and which boxes it reaches (Bug #22295). Under Box, -r and RUBYOPT=-r go through Ruby::Box#require without the RubyGems override, so a regular gem cannot be loaded that way, and a test harness that injects a gem with RUBYOPT=-r fails. The feature also stays in the main box, so a box created later does not see it. Item 3 of Bug #21760 reports the same gap in Ruby::Box#require itself.
  3. What C-backed global variables mean in an optional box (Bug #22307). Assignments to $/, $stdout and the like stay in a box-local table that C never reads. ruby/ruby#18711 makes the main box behave like Ruby without boxes and leaves optional boxes open. $VERBOSE in ruby/ruby#18579 is the same question for one variable.
  4. How prism ships (Bug #22305). The statically linked prism loads in only one box, so a gem that requires it, such as error_highlight, fails in every box but the first.
  5. How JIT hooks reach boxes (Bug #22306). Enabling YJIT switches Array#each and a few other methods to Ruby only in the master box, so the root and main boxes never get them.
  6. What a box's top self should be (Bug #22331). Only the master box's top self has include, using, private and the other top-level definition methods, so Ruby::Box#load and load(file, true) fail.
  7. Where prelude is evaluated (Bug #22332). It runs once in the master box, so pp and binding.irb require into it.

Open pull requests

ruby/ruby

  • ruby/ruby#18579: assignments to $VERBOSE and $DEBUG have no effect (Bug #22282). Waiting on decision 3.
  • ruby/ruby#18711: C-backed global variables in the main box (Bug #22307). It depends on ruby/ruby#18704, which fixes alias $new $old in a box.
  • ruby/ruby#18708: trace_var hooks do not run for assignments in a box.
  • ruby/ruby#18707: the top self of a box lacks the top-level definition methods (Bug #22331).
  • ruby/ruby#18713: pp and binding.irb require into the master box (Bug #22332). Feature #21881 proposed loading prelude.rb per box, and this does it.
  • ruby/ruby#18821: Ractor.new resolves the classes of its arguments in the master box, so passing an instance of a class required in the main box raises undefined class/module. It touches the Ractor and Box integration that Feature #22226#note-6 left to be decided with the Box author.
  • ruby/ruby#18842: a box classext is attached to its class only after it is filled in, so a GC in between frees what it holds.
  • ruby/ruby#18710: RUBY_FREE_AT_EXIT=1 hangs at exit on macOS and crashes on Linux.
  • ruby/ruby#18701: a frozen $LOADED_FEATURES raises FrozenError while the feature index is rebuilt. This is not specific to Box, but every box hits it on its first require.

Fixed

ruby/ruby

ruby/rubygems

  • ruby/rubygems#9892: re-enabled the stdio capture tests under Box once Bug #21867 was fixed.
  • ruby/rubygems#9826: made the RubyGems and Bundler suites green under RUBY_BOX=1 and added the Box lanes to CI.
  • ruby/rubygems#9809: Bundler evaluated gemspecs through TOPLEVEL_BINDING, which always belongs to the main box. It now uses a binding in the box Bundler is loaded in, so gemspecs resolve the right Gem::Specification.
  • ruby/rubygems#9810: the gem CLI died under RUBY_BOX=1. Marshal-based deep copies could not resolve Gem:: constants across boxes and were replaced with a plain deep dup, and RUBY_BOX is now stripped from extension build subprocesses, where mkmf have_devel? recurses until SystemStackError (Bug #22283). It includes a CLI canary test that runs under RUBY_BOX=1.

Related issues 20 (9 open11 closed)

Related to Ruby - Feature #13847: Gem activated problem for default gemsClosedhsbt (Hiroshi SHIBATA)Actions
Related to Ruby - Bug #22123: Ruby::Box + `BUNDLER_SETUP` can evaluate gemspecs before main-box RubyGems initializationClosedActions
Related to Ruby - Bug #21830: Ruby::Box and Kernel#requireClosedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22015: Success without RUBY_BOX=1, Failure with RUBY_BOX=1Closedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22090: Enabling Ruby::Box breaks Marshal.loadClosedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22110: Ruby::Box C-extension loading can be denied by predictable temporary filenameClosedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #21867: enabling Ruby::Box changes puts/warn to no longer use $stdout/$stderrClosedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22271: Ruby::Box fails to load C extensions from deep paths (temporary file name exceeds NAME_MAX)ClosedActions
Related to Ruby - Bug #22282: $VERBOSE and $DEBUG assignments have no effect under RUBY_BOX=1OpenActions
Related to Ruby - Bug #21977: Ruby::Box crash with `RUBY_BOX=1` via `Binding` refinement, `UnboundMethod#bind_call`, and `Symbol#to_proc`Closedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22280: Ruby::Box breaks $? after Kernel#system / IO.popenClosedActions
Related to Ruby - Bug #21760: Ruby::Box: a couple of require-related problemsAssignedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22283: `defined?` does not see global variables assigned in a boxClosedtagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22295: `ruby -r<gem>` does not activate gems under RUBY_BOX=1OpenActions
Related to Ruby - Misc #22296: Ruby::Box: method stubs on core classes are invisible to internal builtinsOpenActions
Related to Ruby - Bug #22305: Ruby::Box: statically linked prism loads in only one boxOpenActions
Related to Ruby - Bug #22306: Ruby::Box: with_jit hooks do not reach existing boxesOpenActions
Related to Ruby - Bug #22307: Ruby::Box: assignments to C-backed global variables do not reach COpenActions
Related to Ruby - Bug #22331: Ruby::Box: a box's top self lacks the top-level definition methodsOpentagomoris (Satoshi Tagomori)Actions
Related to Ruby - Bug #22332: Ruby::Box: prelude is evaluated only in the master box, so pp and binding.irb load into itOpentagomoris (Satoshi Tagomori)Actions
Actions

Also available in: PDF Atom