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 applications and libraries.

Known issues

  • Experimental warning is shown when ruby starts with RUBY_BOX=1 (specify -W:no-experimental option to hide it)
  • bundle install may fail
  • require 'active_support' may fail
  • A wrong current namespace detection happens sometimes in the root namespace

TODOs

  • Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?)
  • Delete per-box extension files (.so) lazily or process exit
  • Collect rb_classext_t entries for a box when ::GC collects the box
  • 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 Entry invisible
  • More test cases about $LOAD_PATH and $LOADED_FEATURES
  • Return classpath and nesting without the namespace prefix in the namespace itself {#21316}, {#21318}

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 namespace. Other values (or unset RUBY_BOX) means disabling namespace. 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 two box types:

  • Root box
  • User boxes

There is the root box, just a single box in a Ruby process. Ruby bootstrap runs in the root box, and all builtin classes/modules are defined 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 namespace automatically created at the end of Ruby's bootstrap, copied from the root box.

When .new is called, an "optional" box (a user, non-main box) is created, copied from the root box. All user boxes are flat, copied from the root box.

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 =~ 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('foo')

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

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

The main box and box 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
  • Including classes/modules loaded by prelude.rb (including RubyGems Gem, for example)

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

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('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('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('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('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('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.

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.

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.

Split root and builtin box

Currently, the single "root" box is the source of classext CoW. And also, the "root" box can load additional files after starting main script evaluation by calling methods which contain lines like require "openssl".

That means, user boxes can have different sets of definitions according to when it is created.

[root]
 |
 |----[main]
 |
 |(require "openssl" called in root)
 |
 |----[box1] having OpenSSL
 |
 |(remove_const called for OpenSSL in root)
 |
 |----[box2] without OpenSSL

This could cause unexpected behavior differences between user boxes. It should NOT be a problem because user scripts which refer to OpenSSL should call require "openssl" by themselves. But in the worst case, a script (without require "openssl") runs well in box1, but doesn't run in box2. This situation looks like a "random failure" to users.

An option possible to prevent this situation is to have "root" and "builtin" boxes.

  • root
    • The box for the Ruby process bootstrap, then the source of CoW
    • After starting the main box, no code runs in this box
  • builtin
    • The box copied from the root box at the same time with "main"
    • Methods and procs defined in the "root" box run in this box
    • Classes and modules required will be loaded in this box

This design realizes a consistent source of box CoW.

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 the list of Modules nested at the point of call.

.new

Creates 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 mod is a subclass of other.

#<=

Returns true if mod is a subclass of other or is the same as other.

#<=>

Returns:

#==

Alias for Object#eql?.

#===

Case Equality—Returns true if obj is an instance of mod or an instance of one of mod’s descendants.

#>

Returns true if mod is an ancestor of other.

#>=

Returns true if mod is an ancestor of other, or the two modules are the same.

#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.

#class_eval

Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected.

#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

Namespace.newBox

Returns a new Box object.

[ GitHub ]

  
# File 'box.c', line 353

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);

    return box_value;
}

Class Attribute Details

Namespace.enabled?Boolean (readonly)

Returns true if Box is enabled.

[ GitHub ]

  
# File 'box.c', line 394

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

Class Method Details

Namespace.currentBox, ...

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

[ GitHub ]

  
# File 'box.c', line 407

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 924

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

.root

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 917

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 939

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));
}

#root?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'box.c', line 931

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 807

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 856

static VALUE
rb_box_inspect(VALUE obj)
{
    rb_box_t *box;
    VALUE r;
    if (obj == Qfalse) {
        r = rb_str_new_cstr("#<Namespace:root>");
        return r;
    }
    box = rb_get_box_t(obj);
    r = rb_str_new_cstr("#<Namespace:");
    rb_str_concat(r, rb_funcall(LONG2NUM(box->box_id), rb_intern("to_s"), 0));
    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)

And it requires calling dlclose before deleting it.

[ GitHub ]

  
# File 'box.c', line 732

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 426

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 744

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 752

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);
}