Skip to content

Commit

Permalink
Refactor: explicit init of Thread/Fiber class variables
Browse files Browse the repository at this point in the history
Replaces the implicit initialization of Thread and Fiber class variables
with an explicit initializer since both should be initialized before
`__crystal_once_init` is called.

This is working for now because the `class_[getter|property]` macros
in Thread and Fiber don't protect against either recursion nor parallel
initializations, but we plan to protect them (using Mutex) which would
immediately break with an infinite recursion:

Mutex#lock -> Fiber#current -> Thread#current -> Mutex#lock -> ...
  • Loading branch information
ysbaddaden committed Jan 24, 2025
1 parent bac59ec commit 39d88fa
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 39d88fa

Please sign in to comment.