-
Notifications
You must be signed in to change notification settings - Fork 1
Execution and Lifecycle
Ruby is a garbage-collected language. The garbage collector may run on any Ruby thread, either when Ruby chooses to invoke it directly before a memory allocation operation, or when it's explicitly invoked by Ruby code, the core library or an extension library. In the current implementation, the Ruby VM does not create any hidden Ruby threads for the purpose of garbage collection, finalizers or any other purpose.
Ruby allows you to register one or more finalizers to any value which is not an immediate value (i.e. object values only). A finalizer is a block which can be called when the object can be destroyed, and it is called with 1 parameter, which is the object id of the object which is being finalized.
An object id is currently implemented as a representation of a pointer to the object's memory location. As such they are a unique identifier for currently available objects - note that when an object is freed from memory e.g. by the garbage collector, it is possible for another object to be created with the same object ID.
Some types of Ruby core and extension library objects, e.g. certain I/O objects, also have a separate type of built-in internal finalizer which invokes externally visible actions, e.g. flushing buffers and closing file descriptors. Internal finalizers will not generally be discussed further here, but note that they are invoked at the time that the object is freed from memory, as described below.
Ruby has 2 mechanisms for invoking finalizers:
- Garbage collection, as part of freeing an object
- Ruby termination
The garbage collector frees objects as follows:
- An object is deemed to be dead when it is no longer possible for any code to access the object. This includes finalizer code - if a registered finalizer block can access the object it is intended to finalize, e.g. through its closure on local variables, then the garbage collector will never free it
- The garbage collector may choose to free dead objects when it is invoked
- If the garbage collector chooses to free an object, then the object's finalizers will be invoked as follows:
- Any one Ruby thread will be selected to finalize the object, and it will run the object's finalizers in the order in which they were registered
- Objects will be freed from memory before their associated finalizers are invoked
- It is undefined whether the thread invoking the finalizer is holding any Mutex locks - if the finalizer locks Mutexes, it must be careful of deadlocking possibilities and similar issues e.g. that the thread it is running in does not already hold a lock it is attempting to acquire
- Some more specific details of the current implementation of the garbage collector:
- Ruby does not currently permit multiple finalizers to be running at once, either on multiple threads or reentrantly (a finalizer run within a finalizer)
- When the garbage collector detects a dead object which has finalizers, the object will be added to a global list of objects with pending finalizers
- During garbage collection, if there are pending finalizers to be run, and no finalizers are currently running, the thread running the garbage collector will be scheduled to execute all pending finalizers at its next interrupt point (as long as at that point no finalizers are running)
- In an explicit invocation of the garbage collector, if no finalizers are currently running, pending finalizers will be run as part of the explicit invocation
- If, after running a list of pending finalizers, there have been more pending finalizers added, these will immediately be run
When a Ruby application is in the process of terminating, Ruby will invoke finalizers as follows:
- All registered finalizers will be run, in an undefined order, while the objects are still live (this includes finalizers which hold references to the object they are finalizing). Any registering or deregistering of finalizers during this process is not guaranteed to have any effect
- Objects which have internal finalizers will have those finalizers invoked
If an exception is thrown out of a finalizer, it is silently caught and discarded (including, oh dear, thread kill and system exit exceptions!).
The Ruby VM initializes itself, and begins executing the specified main sourcefile, as follows. The Ruby interpreter can be invoked with numerous command line options and other configuration parameters (e.g. environment variables) which I will not cover, in general I'll just describe standard initialization.
- All the Ruby core components initialize themselves, creating classes, constants, global variables etc. to expose their API
- A new instance of Object is created to serve as the "top self" object
-
Object::TOPLEVEL_BINDINGis set to a Binding object with: - No local variables
- The
selfvalue being the "top self" object - The class reference stack consisting of
Object - The following files will be loaded via the
requiremechanism: enc/encdb.soenc/trans/transdb.sorubygems.rb- If any libraries are specified to be loaded e.g. on the command line, these are loaded in order via the
requiremechanism - The main sourcefile will be executed under the context of
Object::TOPLEVEL_BINDING, and any local variables defined in the sourcefile will be added toObject::TOPLEVEL_BINDING's dynamic variable context stack before the sourcefile is executed
The Ruby VM will initiate termination processing when the root fiber of the main thread completes execution. Termination processing is then as follows (in the main thread), and note that an exception raised out of any of these actions will be caught and will not prevent execution of subsequent actions:
- Execute an interrupt point (at which any outstanding signal handlers, finalizers or exceptions to be thrown are processed)
- Execute the EXIT signal handler, if registered
- Execute the list of exit handler procs defined by an END{} expression or the
at_exitmethod to be run at VM termination, in the following order: - First those procs defined in a "wrap=true" load context, then the remaining
- In reverse of the order in which they were registered
- Additional exit handler procs may be registered during these executions
- Disable all tracing and coverage
- Disable thread creation
- Unlock all mutexes held by the main thread
- Send a kill signal to all Ruby threads other than the main thread
- Loop as follows until all other threads have terminated:
- Yield Thread control
- Execute an interrupt point, ignoring any exceptions raised
- Set the SIGINT signal to have system-default behaviour (i.e. immediate termination of the process from this point forward if the user presses ctrl-C)
- Invoke all registered finalizers (as described for termination finalizing above)
Ruby will then consider 2 exceptions that may have been thrown:
- the main thread exception, being the exception raised out of either the interrupt point at the start of termination processing, or if that does not exist, any exception raised out of the main thread when it completed processing
- the post termination exception, being the last exception to be raised out of the EXIT signal handler and the "END{}/at_exit" procs
If either the main thread or post termination exception is of class Signal or its subclasses (giving preference to the main thread exception), then the signal number associated with the Signal exception will be immediately raised on the Ruby process with system default handling.
The Ruby process will then exit, with an exit status determined as follows:
- If either the main thread or post termination exception is of class SystemExit or its subclasses (giving preference to the main thread exception), then the exit status is as specified in the exception
- Otherwise, if either the main thread or post termination exception exists, then the exit status will be a predefined failure code (usually 1)
- Otherwise (no exceptions), the exit status will be a predefined success code (usually 0)
A Ruby application may initiate irregular termination by:
- Calling the
Kernel#exitorKernel#abortmethods - Sending a kill instruction to the main thread, which is the same as calling the default implementation of
Kernel#exitwith no parameters
When Kernel#exit or Kernel#abort are called, they simply raise a SystemExit exception containing an exit status value. There is nothing much special about this exception, except:
- If it is thrown out of a non-main thread on that thread's completion, it will then be raised against the main thread
- If it is thrown out of the main thread on its completion, the exception's exit status code will be the exit status of the process.
Therefore, irregular termination can easily be cancelled by catching the SystemExit exception.
A Ruby application may also cause irregular termination using the Kernel#exit! method, which terminates the Ruby process immediately with no termination processing at all.
Ruby allows you to register Ruby blocks to be invoked at certain code points, in order to trace the execution of Ruby code. Tracing blocks can be registered for the whole VM or for a specific thread.
The following events will trigger the registered blocks to be called:
| Event | Description |
|---|---|
| "line" | Invoked each time a line of Ruby code begins executing |
| "class" | Invoked when a class or module expression is entered |
| "end" | Invoked when a class or module expression is exited |
| "call" | Invoked when a method with a Ruby implementation is called |
| "return" | Invoked when a method with a Ruby implementation returns |
| "c-call" | Invoked when a method with a C API implementation is called |
| "c-return" | Invoked when a method with a C API implementation returns |
| "raise" | Invoked when an exception is raised |
When an event occurs, the blocks will be invoked with the following arguments:
- event - the name of the event as per the table above
-
file - the Ruby context's sourcefile's file name, or
nilif no Ruby context -
line - the Ruby context's sourcefile's currently executing line number, or
nilif no Ruby context -
method - the name, in Symbol form, of the currently executing method, or
nilif no method -
binding - a Binding object encapsulating the Ruby context, or
nilif no Ruby context -
class - the class on which method is defined, or
nilif no method
The Ruby context is the Ruby code either directly associated with the event (e.g. the Ruby code corresponding to the "line" event, or the beginning of a Ruby method invocation for a "call" event), or indirectly responsible for the event through a nested sequence of calls to methods/blocks implemented by the C API (e.g. for "c-call", "c-return" and "raise" events invoked by C API code).
Registered blocks are invoked in the following order:
- Thread-specific, then VM-wide
- In reverse of the order that they were registered
While a trace block invocation is in progress, tracing is suspended on that thread.
module GC
module ObjectSpace
These two modules are defined by this API, and may be included in other modules and classes.
GC::start
GC#garbage_collect
ObjectSpace::garbage_collect
private ObjectSpace#garbage_collect
Initiates a garbage collection cycle.
Returns nil.
ObjectSpace::each_object [ <module> ] [ { <block> } ]
private ObjectSpace#each_object [ <module> ] [ { <block> } ]
Iterates through the list of live objects in Ruby and invokes <block> once for each object, with the object as an argument. If <module> is specified, only invokes <block> for objects which are an instance of that module or class (or its subclasses).
Does not see objects which are:
- Immediate type values
- Freed or in the process of freeing (e.g. currently being or scheduled to be finalized)
- Singleton classes
- Internal "hidden" objects created by core/extension libraries
If no <block> is provided, wraps the method invocation in an Enumerator object and returns it. Otherwise, runs the <block> as described and returns the number of objects found.
ObjectSpace::define_finalizer <object>, <proc>
ObjectSpace::define_finalizer <object> { <block> }
private ObjectSpace#define_finalizer <object>, <proc>
private ObjectSpace#define_finalizer <object> { <block> }
Registers a finalizer on <object> which is the provided <block> or <proc>.
If <proc> is provided and it does not respond_to? the call method, raises an error.
If <object> is an immediate type value, raises an error.
Adds the <proc> or <block> to the list of <object>'s finalizers, and returns a new Array containing the current safe level followed by the <proc> or <block> (converted to a Proc).
ObjectSpace::undefine_finalizer <object>
private ObjectSpace#undefine_finalizer <object>
Removes all finalizers from <object> and returns <object>.
BasicObject#__id__
Kernel#object_id
Returns a integer object id representing self, either a Fixnum or Bignum.
If self is freed, any finalizers that are called are guaranteed to be invoked with an identically valued object id as is returned from this method.
All currently active immediate and object type values have distinct object ids, although an object id may be reused over time if its associated value is freed.
ObjectSpace::_id2ref <object_id>
private ObjectSpace#_id2ref <object_id>
Raises an error if <object_id>:
- is not a valid object id
- is associated with an object which has been freed or is in the process of freeing (e.g. currently being or scheduled to be finalized)
Returns the object whose object id is <object_id>.
Module method Kernel at_exit rb_f_at_exit
Kernel::set_trace_func <tracer>
private Kernel#set_trace_func <tracer>
Removes all VM-level trace blocks.
If <tracer> is nil, returns nil. If <tracer> is a Proc, sets <tracer> to be a VM-level trace block and returns <tracer>. Otherwise, raises an error.
Thread#set_trace_func <tracer>
Removes all thread-level trace blocks from self.
If <tracer> is nil, returns nil. If <tracer> is a Proc, sets <tracer> to be a thread-level trace block on self and returns <tracer>. Otherwise, raises an error.
Thread#add_trace_func <tracer>
If <tracer> is nil, returns nil. If <tracer> is a Proc, adds <tracer> to be a thread-level trace block on self and returns <tracer>. Otherwise, raises an error.
A Ruby Language Reference
Copyright © by Michael Hore, 2016.
Introduction to This Document
Ruby Elements
- Classes and Modules
- Methods
- Blocks, Procs and Lambdas
- Execution Context and Closures
- Variables, Constants and Namespaces
- Types and Literals
- Ruby Expressions
- Operators
Syntax Grammar
Exceptions and Throw
Ruby Sourcefiles and Libraries
Multi Threading
Execution and Lifecycle