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