Skip to content

Commit

Permalink
Refactor explicit init of class var for Thread, Fiber in `Crystal…
Browse files Browse the repository at this point in the history
….init_runtime` (#15369)

Adds `Crystal.init_runtime` which calls `Thread.init`, `Fiber.init` to explicitly initialize the class variables defined on `Thread` and `Fiber`.

In order to implement #14905 we'll need the ability to access `Fiber.current` which depends on `Thread.current` but if these accessors use the class getter/property macros then we'll enter an infinite recursion.
  • Loading branch information
ysbaddaden authored Jan 25, 2025
1 parent 1bc54ca commit 9e475f6
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 22 deletions.
17 changes: 15 additions & 2 deletions src/crystal/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ end
module Crystal
# Defines the main routine run by normal Crystal programs:
#
# - Initializes the GC
# - Initializes runtime requirements (GC, ...)
# - Invokes the given *block*
# - Handles unhandled exceptions
# - Invokes `at_exit` handlers
Expand Down Expand Up @@ -37,6 +37,8 @@ module Crystal
{% if flag?(:tracing) %} Crystal::Tracing.init {% end %}
GC.init

init_runtime

status =
begin
yield
Expand All @@ -48,6 +50,14 @@ module Crystal
exit(status, ex)
end

# :nodoc:
def self.init_runtime : Nil
# `__crystal_once` directly or indirectly depends on `Fiber` and `Thread`
# so we explicitly initialize their class vars
Thread.init
Fiber.init
end

# :nodoc:
def self.exit(status : Int32, exception : Exception?) : Int32
status = Crystal::AtExitHandlers.run status, exception
Expand Down Expand Up @@ -130,7 +140,10 @@ fun main(argc : Int32, argv : UInt8**) : Int32
Crystal.main(argc, argv)
end

{% if flag?(:win32) %}
{% if flag?(:interpreted) %}
# the interpreter doesn't call Crystal.main(&)
Crystal.init_runtime
{% elsif flag?(:win32) %}
require "./system/win32/wmain"
{% elsif flag?(:wasi) %}
require "./system/wasi/main"
Expand Down
13 changes: 12 additions & 1 deletion src/crystal/system/thread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
module Crystal::System::Thread
# alias Handle

# def self.init : Nil

# def self.new_handle(thread_obj : ::Thread) : Handle

# def self.current_handle : Handle
Expand Down Expand Up @@ -48,7 +50,16 @@ class Thread
include Crystal::System::Thread

# all thread objects, so the GC can see them (it doesn't scan thread locals)
protected class_getter(threads) { Thread::LinkedList(Thread).new }
@@threads = uninitialized Thread::LinkedList(Thread)

protected def self.threads : Thread::LinkedList(Thread)
@@threads
end

def self.init : Nil
@@threads = Thread::LinkedList(Thread).new
Crystal::System::Thread.init
end

@system_handle : Crystal::System::Thread::Handle
@exception : Exception?
Expand Down
29 changes: 20 additions & 9 deletions src/crystal/system/unix/pthread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ module Crystal::System::Thread
raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) unless ret == 0
end

def self.init : Nil
{% if flag?(:musl) %}
@@main_handle = current_handle
{% elsif flag?(:openbsd) || flag?(:android) %}
ret = LibC.pthread_key_create(out current_key, nil)
raise RuntimeError.from_os_error("pthread_key_create", Errno.new(ret)) unless ret == 0
@@current_key = current_key
{% end %}
end

def self.thread_proc(data : Void*) : Void*
th = data.as(::Thread)

Expand Down Expand Up @@ -53,13 +63,7 @@ module Crystal::System::Thread
# Android appears to support TLS to some degree, but executables fail with
# an underaligned TLS segment, see https://github.com/crystal-lang/crystal/issues/13951
{% if flag?(:openbsd) || flag?(:android) %}
@@current_key : LibC::PthreadKeyT

@@current_key = begin
ret = LibC.pthread_key_create(out current_key, nil)
raise RuntimeError.from_os_error("pthread_key_create", Errno.new(ret)) unless ret == 0
current_key
end
@@current_key = uninitialized LibC::PthreadKeyT

def self.current_thread : ::Thread
if ptr = LibC.pthread_getspecific(@@current_key)
Expand All @@ -84,11 +88,18 @@ module Crystal::System::Thread
end
{% else %}
@[ThreadLocal]
class_property current_thread : ::Thread { ::Thread.new }
@@current_thread : ::Thread?

def self.current_thread : ::Thread
@@current_thread ||= ::Thread.new
end

def self.current_thread? : ::Thread?
@@current_thread
end

def self.current_thread=(@@current_thread : ::Thread)
end
{% end %}

def self.sleep(time : ::Time::Span) : Nil
Expand Down Expand Up @@ -169,7 +180,7 @@ module Crystal::System::Thread
end

{% if flag?(:musl) %}
@@main_handle : Handle = current_handle
@@main_handle = uninitialized Handle

def self.current_is_main?
current_handle == @@main_handle
Expand Down
14 changes: 13 additions & 1 deletion src/crystal/system/wasi/thread.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module Crystal::System::Thread
alias Handle = Nil

def self.init : Nil
end

def self.new_handle(thread_obj : ::Thread) : Handle
raise NotImplementedError.new("Crystal::System::Thread.new_handle")
end
Expand All @@ -13,7 +16,16 @@ module Crystal::System::Thread
raise NotImplementedError.new("Crystal::System::Thread.yield_current")
end

class_property current_thread : ::Thread { ::Thread.new }
def self.current_thread : ::Thread
@@current_thread ||= ::Thread.new
end

def self.current_thread? : ::Thread?
@@current_thread
end

def self.current_thread=(@@current_thread : ::Thread)
end

def self.sleep(time : ::Time::Span) : Nil
req = uninitialized LibC::Timespec
Expand Down
27 changes: 19 additions & 8 deletions src/crystal/system/win32/thread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ module Crystal::System::Thread
)
end

def self.init : Nil
{% if flag?(:gnu) %}
current_key = LibC.TlsAlloc
if current_key == LibC::TLS_OUT_OF_INDEXES
Crystal::System.panic("TlsAlloc()", WinError.value)
end
@@current_key = current_key
{% end %}
end

def self.thread_proc(data : Void*) : LibC::UInt
# ensure that even in the case of stack overflow there is enough reserved
# stack space for recovery (for the main thread this is done in
Expand Down Expand Up @@ -47,13 +57,7 @@ module Crystal::System::Thread

# MinGW does not support TLS correctly
{% if flag?(:gnu) %}
@@current_key : LibC::DWORD = begin
current_key = LibC.TlsAlloc
if current_key == LibC::TLS_OUT_OF_INDEXES
Crystal::System.panic("TlsAlloc()", WinError.value)
end
current_key
end
@@current_key = uninitialized LibC::DWORD

def self.current_thread : ::Thread
th = current_thread?
Expand Down Expand Up @@ -82,11 +86,18 @@ module Crystal::System::Thread
end
{% else %}
@[ThreadLocal]
class_property current_thread : ::Thread { ::Thread.new }
@@current_thread : ::Thread?

def self.current_thread : ::Thread
@@current_thread ||= ::Thread.new
end

def self.current_thread? : ::Thread?
@@current_thread
end

def self.current_thread=(@@current_thread : ::Thread)
end
{% end %}

def self.sleep(time : ::Time::Span) : Nil
Expand Down
10 changes: 9 additions & 1 deletion src/fiber.cr
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,16 @@ end
# notifications that IO is ready or a timeout reached. When a fiber can be woken,
# the event loop enqueues it in the scheduler
class Fiber
@@fibers = uninitialized Thread::LinkedList(Fiber)

protected def self.fibers : Thread::LinkedList(Fiber)
@@fibers
end

# :nodoc:
protected class_getter(fibers) { Thread::LinkedList(Fiber).new }
def self.init : Nil
@@fibers = Thread::LinkedList(Fiber).new
end

@context : Context
@stack : Void*
Expand Down

0 comments on commit 9e475f6

Please sign in to comment.