123456789_123456789_123456789_123456789_123456789_

Class: Ruby::Box

Relationships & Source Files
Namespace Children
Modules:
Classes:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::Module
Instance Chain:
self, ::Module
Inherits: Module
Defined in: box.c,
box.c

Overview

::Ruby Box - Ruby's in-process separation of Classes and Modules

::Ruby Box is designed to provide separated spaces in a ::Ruby process, to isolate application code, libraries and monkey patches.

Known issues

  • Experimental warning is shown when ruby starts with RUBY_BOX=1 (specify -W:no-experimental option to hide it)
  • Installing native extensions may fail under RUBY_BOX=1 because of stack level too deep in extconf.rb
  • require 'active_support/core_ext' may fail under RUBY_BOX=1
  • Defined methods in a box may not be referred by built-in methods written in ::Ruby

TODOs

  • Add the loaded box on iseq to check if another box tries running the iseq (add a field only when VM_CHECK_MODE?)
  • Assign its own TOPLEVEL_BINDING in boxes
  • Fix calling warn in boxes to refer $VERBOSE and Warning.warn in the box
  • Make an internal data container class Entry invisible
  • More test cases about $LOAD_PATH and $LOADED_FEATURES

How to use

Enabling Ruby Box

First, an environment variable should be set at the ruby process bootup: RUBY_BOX=1. The only valid value is 1 to enable ::Ruby Box. Other values (or unset RUBY_BOX) means disabling ::Ruby Box. And setting the value after ::Ruby program starts doesn't work.

Using Ruby Box

Box class is the entrypoint of ::Ruby Box.

box = Ruby::Box.new
box.require('something') # or require_relative, load

The required file (either .rb or .so/.dll/.bundle) is loaded in the box (box here). The required/loaded files from something will be loaded in the box recursively.

# something.rb

X = 1

class Something
  def self.x = X
  def x = ::X
end

Classes/modules, those methods and constants defined in the box can be accessed via box object.

X = 2
p X                 # 2
p ::X               # 2
p box::Something.x  # 1
p box::X            # 1

Instance methods defined in the box also run with definitions in the box.

s = box::Something.new

p s.x  # 1

Specifications

Ruby Box types

There are three box types:

  • Master box
  • Root box
  • User boxes

Ruby bootstrap runs in the root box, and a

There is the root box, just a single box in a Ruby process. All builtin classes/modules are defined and run in the root box. (See "Builtin classes and modules".)

User boxes are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the ruby command line argument) is executed in the "main" box, which is a user box automatically created at the end of Ruby's bootstrap. The files specified with -r command line option will be required in the main box.

Calling .new creates an "optional" box (a user, non-main box), technically equal to the main box.

Ruby also has the master box. The master box is the "master copy" of all boxes. Boxes will be created as a copy of the master box. The master box is only for the source of box copies, and no code runs in the master box.

[master]
 |
 |----[root]
 |
 |----[main]
 |
 |----[user box 1]
 |
 |----[user box 2]
 #...

Ruby Box class and instances

Box is a class, as a subclass of ::Module. Box instances are a kind of ::Module.

Classes and modules defined in boxes

The classes and modules, newly defined in a box box, are accessible via box. For example, if a class A is defined in box, it is accessible as box::A from outside of the box.

In the box box, A can be referred to as A (and ::A).

Built-in classes and modules reopened in boxes

In boxes, builtin classes/modules are visible and can be reopened. Those classes/modules can be reopened using class or module clauses, and class/module definitions can be changed.

The changed definitions are visible only in the box. In other boxes, builtin classes/modules and those instances work without changed definitions.

# in foo.rb
class String
  BLANK_PATTERN = /\A\s*\z/
  def blank?
    self.match?(BLANK_PATTERN)
  end
end

module Foo
  def self.foo = "foo"

  def self.foo_is_blank?
    foo.blank?
  end
end

Foo.foo.blank? #=> false
"foo".blank?   #=> false

# in main.rb
box = Ruby::Box.new
box.require_relative('foo')

box::Foo.foo_is_blank? #=> false   (#blank? called in box)

"foo".blank?          # NoMethodError
String::BLANK_PATTERN # NameError

The main box and box above are different boxes, so monkey patches in main are also invisible in box.

Builtin classes and modules

In the box context, "builtin" classes and modules are classes and modules:

  • Accessible without any #require calls in user scripts
  • Defined before any user program start running

Hereafter, "builtin classes and modules" will be referred to as just "builtin classes".

Builtin classes and modules are loaded in all boxes, and run in the root box.

Exceptional non-built-in classes/modules

There are some exceptional classes/modules that are enabled in default, but aren't built-in classes. Those classes/modules are:

  • RubyGems
  • ErrorHighlight
  • DidYouMean
  • SyntaxSuggest

Those classes/modules (part of default gems) are loaded in each boxes independently. If a user box's code calls RubyGems, it calls the RubyGems inside the box itself, instead of the root box's one.

Builtin classes referred via box objects

Builtin classes in a box box can be referred from other boxes. For example, box::String is a valid reference, and ::String and box::String are identical (String == box::String, String.object_id == box::String.object_id).

box::String-like reference returns just a ::String in the current box, so its definition is ::String in the box, not in box.

# foo.rb
class String
  def self.foo = "foo"
end

# main.rb
box = Ruby::Box.new
box.require_relative('foo')

box::String.foo  # NoMethodError

Class instance variables, class variables, constants

Builtin classes can have different sets of class instance variables, class variables and constants between boxes.

# foo.rb
class Array
  @v = "foo"
  @@v = "_foo_"
  V = "FOO"
end

Array.instance_variable_get(:@v) #=> "foo"
Array.class_variable_get(:@@v)   #=> "_foo_"
Array.const_get(:V)              #=> "FOO"

# main.rb
box = Ruby::Box.new
box.require_relative('foo')

Array.instance_variable_get(:@v) #=> nil
Array.class_variable_get(:@@v)   # NameError
Array.const_get(:V)              # NameError

Global variables

In boxes, changes on global variables are also isolated in the boxes. Changes on global variables in a box are visible/applied only in the box.

# foo.rb
$foo = "foo"
$VERBOSE = nil

puts "This appears: '#{$foo}'"

# main.rb
p $foo      #=> nil
p $VERBOSE  #=> false

box = Ruby::Box.new
box.require_relative('foo')  # "This appears: 'foo'"

p $foo      #=> nil
p $VERBOSE  #=> false

Top level constants

Usually, top level constants are defined as constants of ::Object. In boxes, top level constants are constants of ::Object in the box. And the box object box's constants are strictly equal to constants of ::Object.

# foo.rb
FOO = 100

FOO         #=> 100
Object::FOO #=> 100

# main.rb
box = Ruby::Box.new
box.require_relative('foo')

box::FOO      #=> 100

FOO          # NameError
Object::FOO  # NameError

Top level methods

Top level methods are private instance methods of ::Object, in each box.

# foo.rb
def yay = "foo"

class Foo
  def self.say = yay
end

Foo.say #=> "foo"
yay     #=> "foo"

# main.rb
box = Ruby::Box.new
box.require_relative('foo')

box::Foo.say  #=> "foo"

yay  # NoMethodError

There is no way to expose top level methods in boxes to others. (See "Expose top level methods as a method of the box object" in "Discussions" section below)

Ruby Box scopes

Ruby Box works in file scope. One .rb file runs in a single box.

Once a file is loaded in a box box, all methods/procs defined/created in the file run in box.

Utility methods

Several methods are available for trying/testing Ruby Box.

  • .current returns the current box
  • .enabled? returns true/false to represent RUBY_BOX=1 is specified or not
  • .root returns the root box
  • .main returns the main box
  • #eval evaluates a Ruby code (String) in the receiver box, just like calling #load with a file

Implementation details

ISeq inline method/constant cache

As described above in "Ruby Box scopes", an ".rb" file runs in a box. So method/constant resolution will be done in a box consistently.

That means ISeq inline caches work well even with boxes. Otherwise, it's a bug.

Method call global cache (gccct)

rb_funcall() C function refers to the global cc cache table (gccct), and the cache key is calculated with the current box.

So, rb_funcall() calls have a performance penalty when Ruby Box is enabled.

Current box and loading box

The current box is the box that the executing code is in. .current returns the current box object.

The loading box is an internally managed box to determine the box to load newly required/loaded files. For example, box is the loading box when box.require("foo") is called.

Discussions

More builtin methods written in Ruby

If Ruby Box is enabled by default, builtin methods can be written in Ruby because it can't be overridden by users' monkey patches. Builtin Ruby methods can be JIT-ed, and it could bring performance reward.

Monkey patching methods called by builtin methods

Builtin methods sometimes call other builtin methods. For example, Hash#map calls Hash#each to retrieve entries to be mapped. Without Ruby Box, Ruby users can overwrite Hash#each and expect the behavior change of Hash#map as a result.

But with boxes, Hash#map runs in the root box. Ruby users can define Hash#each only in user boxes, so users cannot change Hash#map's behavior in this case. To achieve it, users should override bothHash#map and Hash#each (or only Hash#map).

It is a breaking change.

Users can define methods using Ruby::Box.root.eval(...), but it's clearly not ideal API.

Assigning values to global variables used by builtin methods

Similar to monkey patching methods, global variables assigned in a box is separated from the root box. Methods defined in the root box referring a global variable can't find the re-assigned one.

Context of $LOAD_PATH and $LOADED_FEATURES

Global variables $LOAD_PATH and $LOADED_FEATURES control #require method behaviors. So those variables are determined by the loading box instead of the current box.

This could potentially conflict with the user's expectations. We should find the solution.

Expose top level methods as a method of the box object

Currently, top level methods in boxes are not accessible from outside of the box. But there might be a use case to call other box's top level methods.

Separate cc_tbl and callable_m_tbl, cvc_tbl for less classext CoW

The fields of rb_classext_t contains several cache(-like) data, cc_tbl(callcache table), callable_m_tbl(table of resolved complemented methods) and cvc_tbl(class variable cache table).

The classext CoW is triggered when the contents of rb_classext_t are changed, including cc_tbl, callable_m_tbl, and cvc_tbl. But those three tables are changed by just calling methods or referring class variables. So, currently, classext CoW is triggered much more times than the original expectation.

If we can move those three tables outside of rb_classext_t, the number of copied rb_classext_t will be much less than the current implementation.

Class Attribute Summary

Class Method Summary

::Module - Inherited

.constants

In the first form, returns an array of the names of all constants accessible from the point of call.

.nesting

Returns nested module as an array of ::Module objects:

.new

Returns a new anonymous module.

.used_modules

Returns an array of all modules used in the current scope.

.used_refinements

Returns an array of all modules used in the current scope.

Instance Attribute Summary

::Module - Inherited

#singleton_class?

Returns true if mod is a singleton class or false if it is an ordinary class or module.

Instance Method Summary

::Module - Inherited

#<

Returns true if self is a descendant of other (self is a subclass of other or self includes other):

#<=

Compares self and other with respect to ancestry and inclusion.

#<=>

Compares self and other.

#==

Alias for Object#eql?.

#===

Returns whether other is an instance of self, or is an instance of a subclass of self.

#>

Returns true if self is an ancestor of other (self is a superclass of other or self is included in other):

#>=

Compares self and other with respect to ancestry and inclusion.

#alias_method

Makes new_name a new copy of the method old_name.

#ancestors

Returns a list of modules included/prepended in mod (including mod itself).

#attr

The first form is equivalent to #attr_reader.

#attr_accessor

Defines a named attribute for this module, where the name is symbol.id2name, creating an instance variable (@name) and a corresponding access method to read it.

#attr_reader

Creates instance variables and corresponding methods that return the value of each instance variable.

#attr_writer

Creates an accessor method to allow assignment to the attribute symbol.id2name.

#autoload

Registers filename to be loaded (using Kernel.require) the first time that const (which may be a ::String or a symbol) is accessed in the namespace of mod.

#autoload?

Returns filename to be loaded if name is registered as autoload in the namespace of mod or one of its ancestors.

#autoload_relative

Registers filename to be loaded (using Kernel.require) the first time that const (which may be a ::String or a symbol) is accessed in the namespace of mod.

#class_eval

Evaluates the string or block in the context of self.

#class_exec

Evaluates the given block in the context of the class/module.

#class_variable_defined?

Returns true if the given class variable is defined in obj.

#class_variable_get

Returns the value of the given class variable (or throws a ::NameError exception).

#class_variable_set

Sets the class variable named by symbol to the given object.

#class_variables

Returns an array of the names of class variables in mod.

#const_defined?

Says whether mod or its ancestors have a constant with the given name:

#const_get

Checks for a constant with the given name in mod.

#const_missing

Invoked when a reference is made to an undefined constant in mod.

#const_set

Sets the named constant to the given object, returning that object.

#const_source_location

Returns the ::Ruby source filename and line number containing the definition of the constant specified.

#constants

Returns an array of the names of the constants accessible in mod.

#define_method

Defines an instance method in the receiver.

#deprecate_constant

Makes a list of existing constants deprecated.

#freeze

Prevents further modifications to mod.

#include

Invokes Module.append_features on each parameter in reverse order.

#include?

Returns true if module is included or prepended in mod or one of mod's ancestors.

#included_modules

Returns the list of modules included or prepended in mod or one of mod's ancestors.

#inspect

Alias for Module#to_s.

#instance_method

Returns an ::UnboundMethod representing the given instance method in mod.

#instance_methods

Returns an array containing the names of the public and protected instance methods in the receiver.

#method_defined?

Returns true if the named method is defined by mod.

#module_eval
#module_exec
#name

Returns the name of the module mod.

#prepend

Invokes Module.prepend_features on each parameter in reverse order.

#private_class_method

Makes existing class methods private.

#private_constant

Makes a list of existing constants private.

#private_instance_methods

Returns a list of the private instance methods defined in mod.

#private_method_defined?

Returns true if the named private method is defined by mod.

#protected_instance_methods

Returns a list of the protected instance methods defined in mod.

#protected_method_defined?

Returns true if the named protected method is defined mod.

#public_class_method

Makes a list of existing class methods public.

#public_constant

Makes a list of existing constants public.

#public_instance_method

Similar to instance_method, searches public method only.

#public_instance_methods

Returns a list of the public instance methods defined in mod.

#public_method_defined?

Returns true if the named public method is defined by mod.

#refinements

Returns an array of ::Refinement defined within the receiver.

#remove_class_variable

Removes the named class variable from the receiver, returning that variable's value.

#remove_method

Removes the method identified by symbol from the current class.

#set_temporary_name

Sets the temporary name of the module.

#to_s

Returns a string representing this module or class.

#undef_method

Prevents the current class from responding to calls to the named method.

#undefined_instance_methods

Returns a list of the undefined instance methods defined in mod.

#append_features

When this module is included in another, ::Ruby calls #append_features in this module, passing it the receiving module in mod.

#const_added

Invoked as a callback whenever a constant is assigned on the receiver.

#extend_object

Extends the specified object by adding this module's constants and methods (which are added as singleton methods).

#extended

The equivalent of included, but for extended modules.

#included

Callback invoked whenever the receiver is included in another module or class.

#method_added

Invoked as a callback whenever an instance method is added to the receiver.

#method_removed

Invoked as a callback whenever an instance method is removed from the receiver.

#method_undefined

Invoked as a callback whenever an instance method is undefined from the receiver.

#module_function

Creates module functions for the named methods.

#prepend_features

When this module is prepended in another, ::Ruby calls #prepend_features in this module, passing it the receiving module in mod.

#prepended

The equivalent of included, but for prepended modules.

#private

With no arguments, sets the default visibility for subsequently defined methods to private.

#protected

Sets the visibility of a section or of a list of method names as protected.

#public

With no arguments, sets the default visibility for subsequently defined methods to public.

#refine

Refine mod in the receiver.

#remove_const

Removes the definition of the given constant, returning that constant's previous value.

#ruby2_keywords

For the given method names, marks the method as passing keywords through a normal argument splat.

#using

Import class refinements from module into the current class or module definition.

#initialize_clone,
#with_jit

Internal helper for built-in initializations to define methods only when JIT is enabled.

Constructor Details

.newBox

Returns a new Box object.

[ GitHub ]

  
# File 'box.c', line 393

static VALUE
box_initialize(VALUE box_value)
{
    rb_box_t *box;
    rb_classext_t *object_classext;
    VALUE entry;
    ID id_box_entry;
    CONST_ID(id_box_entry, "__box_entry__");

    if (!rb_box_available()) {
        rb_raise(rb_eRuntimeError, "Ruby Box is disabled. Set RUBY_BOX=1 environment variable to use Ruby::Box.");
    }

    entry = rb_class_new_instance_pass_kw(0, NULL, rb_cBoxEntry);
    box = get_box_struct_internal(entry);

    box->box_object = box_value;
    box->box_id = box_generate_id();
    rb_define_singleton_method(box->load_path, "resolve_feature_path", rb_resolve_feature_path, 1);

    // Set the Ruby::Box object unique/consistent from any boxes to have just single
    // constant table from any view of every (including main) box.
    // If a code in the box adds a constant, the constant will be visible even from root/main.
    RCLASS_SET_PRIME_CLASSEXT_WRITABLE(box_value, true);

    // Get a clean constant table of Object even by writable one
    // because ns was just created, so it has not touched any constants yet.
    object_classext = RCLASS_EXT_WRITABLE_IN_BOX(rb_cObject, box);
    RCLASS_SET_CONST_TBL(box_value, RCLASSEXT_CONST_TBL(object_classext), true);

    rb_ivar_set(box_value, id_box_entry, entry);

    if (ruby_box_init_done) {
        if (box_gem_flags->gem) {
            rb_vm_call_cfunc_in_box(Qnil, rb_define_gem_modules, (VALUE)box_gem_flags, Qnil,
                                    rb_str_new_cstr("before_prelude.user.dummy"), (const rb_box_t *)box);
            rb_load_gem_prelude((VALUE)box);
        }
    }

    // Invalidate ZJIT code that assumes only the root box is active
    rb_zjit_invalidate_root_box();

    return box_value;
}

Class Attribute Details

.enabled?Boolean (readonly)

Returns true if Box is enabled.

[ GitHub ]

  
# File 'box.c', line 445

static VALUE
rb_box_s_getenabled(VALUE recv)
{
    return RBOOL(rb_box_available());
}

Class Method Details

.currentBox, ...

Returns the current box. Returns nil if ::Ruby Box is not enabled.

[ GitHub ]

  
# File 'box.c', line 458

static VALUE
rb_box_s_current(VALUE recv)
{
    const rb_box_t *box;

    if (!rb_box_available())
        return Qnil;

    box = rb_vm_current_box(GET_EC());
    VM_ASSERT(box && box->box_object);
    return box->box_object;
}

.main

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 1077

static VALUE
rb_box_s_main(VALUE recv)
{
    return main_box->box_object;
}

.master

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 1063

static VALUE
rb_box_s_master(VALUE recv)
{
    return master_box->box_object;
}

.root

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 1070

static VALUE
rb_box_s_root(VALUE recv)
{
    return root_box->box_object;
}

Instance Attribute Details

#main?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 1100

static VALUE
rb_box_main_p(VALUE box_value)
{
    const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value);
    return RBOOL(BOX_MAIN_P(box));
}

#master?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 1084

static VALUE
rb_box_master_p(VALUE box_value)
{
    const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value);
    return RBOOL(BOX_MASTER_P(box));
}

#root?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 1092

static VALUE
rb_box_root_p(VALUE box_value)
{
    const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value);
    return RBOOL(BOX_ROOT_P(box));
}

Instance Method Details

#eval(str)

[ GitHub ]

  
# File 'box.c', line 933

static VALUE
rb_box_eval(VALUE box_value, VALUE str)
{
    const rb_iseq_t *iseq;
    const rb_box_t *box;

    StringValue(str);

    iseq = rb_iseq_compile_iseq(str, rb_str_new_cstr("eval"));
    VM_ASSERT(iseq);

    box = (const rb_box_t *)rb_get_box_t(box_value);

    return rb_iseq_eval(iseq, box);
}

#inspect

[ GitHub ]

  
# File 'box.c', line 999

static VALUE
rb_box_inspect(VALUE obj)
{
    rb_box_t *box;
    VALUE r;
    if (obj == Qfalse) {
        r = rb_str_new_cstr("#<Ruby::Box:master>");
        return r;
    }
    box = rb_get_box_t(obj);
    r = rb_str_new_cstr("#<Ruby::Box:");
    rb_str_concat(r, rb_funcall(LONG2NUM(box->box_id), rb_intern("to_s"), 0));
    if (BOX_MASTER_P(box)) {
        rb_str_cat_cstr(r, ",master");
    }
    if (BOX_ROOT_P(box)) {
        rb_str_cat_cstr(r, ",root");
    }
    if (BOX_USER_P(box)) {
        rb_str_cat_cstr(r, ",user");
    }
    if (BOX_MAIN_P(box)) {
        rb_str_cat_cstr(r, ",main");
    }
    else if (BOX_OPTIONAL_P(box)) {
        rb_str_cat_cstr(r, ",optional");
    }
    rb_str_cat_cstr(r, ">");
    return r;
}

#load(*args)

[ GitHub ]

  
# File 'box.c', line 855

static VALUE
rb_box_load(int argc, VALUE *argv, VALUE box)
{
    VALUE fname, wrap;
    rb_scan_args(argc, argv, "11", &fname, &wrap);

    rb_vm_frame_flag_set_box_require(GET_EC());

    VALUE args = rb_ary_new_from_args(2, fname, wrap);
    return rb_load_entrypoint(args);
}

#load_pathArray

Returns box local load path.

[ GitHub ]

  
# File 'box.c', line 477

static VALUE
rb_box_load_path(VALUE box)
{
    VM_ASSERT(BOX_OBJ_P(box));
    return rb_get_box_t(box)->load_path;
}

#require(fname)

[ GitHub ]

  
# File 'box.c', line 867

static VALUE
rb_box_require(VALUE box, VALUE fname)
{
    rb_vm_frame_flag_set_box_require(GET_EC());

    return rb_require_string(fname);
}

#require_relative(fname)

[ GitHub ]

  
# File 'box.c', line 875

static VALUE
rb_box_require_relative(VALUE box, VALUE fname)
{
    rb_vm_frame_flag_set_box_require(GET_EC());

    return rb_require_relative_entrypoint(fname);
}