Skip to content

Commit

Permalink
Fix: register GC callbacks inside GC.init (#15278)
Browse files Browse the repository at this point in the history
Running the std specs would immediately hang with the `master` branch
of the GC, waiting on `GC.lock_write`: the GC is trying to collect
while Crystal is initializing constants and class variables
(`__crystal_main`) and it would lock the rwlock for write twice, never
unlocking it, before the start callback was registered, but not the
before collect callback (oops).

This patch:

1. Registers the GC callbacks right in the GC.init method; this makes
   sure all callbacks are registered before we start running any Crystal
   code.

2. Initializes the rwlock inside the GC.init method; this avoids a lazy
   initializer and makes sure the memory is properly initialized ASAP.
  • Loading branch information
ysbaddaden authored Dec 15, 2024
1 parent 9183bb0 commit 46459d6
Showing 1 changed file with 24 additions and 20 deletions.
44 changes: 24 additions & 20 deletions src/gc/boehm.cr
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ end

module GC
{% if flag?(:preview_mt) %}
@@lock = Crystal::RWLock.new
@@lock = uninitialized Crystal::RWLock
{% end %}

# :nodoc:
Expand Down Expand Up @@ -205,10 +205,33 @@ module GC
{% end %}
LibGC.init

{% if flag?(:preview_mt) %}
@@lock = Crystal::RWLock.new
{% end %}

LibGC.set_start_callback -> do
GC.lock_write
end

# pushes the stack of pending fibers when the GC wants to collect memory:
{% unless flag?(:interpreted) %}
GC.before_collect do
Fiber.unsafe_each do |fiber|
fiber.push_gc_roots unless fiber.running?
end

{% if flag?(:preview_mt) %}
Thread.unsafe_each do |thread|
if fiber = thread.current_fiber?
GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom)
end
end
{% end %}

GC.unlock_write
end
{% end %}

{% if flag?(:tracing) %}
if ::Crystal::Tracing.enabled?(:gc)
set_on_heap_resize_proc
Expand Down Expand Up @@ -462,25 +485,6 @@ module GC
end
end

# pushes the stack of pending fibers when the GC wants to collect memory:
{% unless flag?(:interpreted) %}
GC.before_collect do
Fiber.unsafe_each do |fiber|
fiber.push_gc_roots unless fiber.running?
end

{% if flag?(:preview_mt) %}
Thread.unsafe_each do |thread|
if fiber = thread.current_fiber?
GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom)
end
end
{% end %}

GC.unlock_write
end
{% end %}

# :nodoc:
def self.stop_world : Nil
LibGC.stop_world_external
Expand Down

0 comments on commit 46459d6

Please sign in to comment.