Skip to content

Organize runtime initialization #17008

Description

@straight-shoota

Runtime initialization is currently spread over a number of different locations in the prelude, each executing at different times.

For example, Crystal.init_runtime only initialize Crystal::Once as well as the Fiber and Thread lists.

# :nodoc:
def self.init_runtime : Nil
# `__crystal_once` directly or indirectly depends on `Fiber` and `Thread`
# so we explicitly initialize their class vars, then init crystal/once
Thread.init
Fiber.init
Crystal::Once.init
end

kernel.cr initializes execution context / scheduler, callstack and signal handlers in top-level user code:

crystal/src/kernel.cr

Lines 602 to 627 in 8365f55

{% unless flag?(:interpreted) || flag?(:wasm32) %}
{% if flag?(:execution_context) %}
Fiber::ExecutionContext.init_default_context
{% else %}
Crystal::Scheduler.init
{% end %}
# load debug info on start up of the program is executed with CRYSTAL_LOAD_DEBUG_INFO=1
# this will make debug info available on print_frame that is used by Crystal's segfault handler
#
# - CRYSTAL_LOAD_DEBUG_INFO=0 will never use debug info (See Exception::CallStack.load_debug_info)
# - CRYSTAL_LOAD_DEBUG_INFO=1 will load debug info on startup
# - Other values will load debug info on demand: when the backtrace of the first exception is generated
Exception::CallStack.load_debug_info if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "1"
Exception::CallStack.setup_crash_handler
{% if flag?(:win32) %}
Crystal::System::Process.start_interrupt_loop
{% else %}
Crystal::System::Signal.setup_default_handlers
{% end %}
{% end %}
{% if flag?(:interpreted) && flag?(:unix) && Crystal::Interpreter.class.has_method?(:signal_descriptor) %}
Crystal::System::Signal.setup_default_handlers
{% end %}

The odd thing is that this top-level user code is defined somewhere in the alphabetically-sorted require list from prelude.cr. It happens to work, but it would fail if we try to use IO in top-level code from any of the previously required files.

It would be nice to define the runtime startup sequence a bit more explicitly, and document the exact conditions for hooking into Crystal.main and Crystal.main_user_code.

There are use cases that require to run code at process startup when it is guaranteed to be single-threaded to avoid any threading issues (ENV modifications, fork). They might require different levels of runtime initialization. For the former, a working event loop would be nice, for example.

We should probably move all stdlib initialization code into methods instead of top-level user code. We can call these methods explicitly from Crystal.main/Crystal.main_user_code in order to establish a clear init sequence.


Add a 👍 reaction to issues you find important.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions