What is "GC Warning: Finalization cycle involving"

I am working on a Crystal application that is integrating with Z3 a c library. When doing some performance testing and increasing load I keep getting this warning GC Warning: Finalization cycle involving 0x7f6d1fa9c900. Doing some googling it seems like with this error some objects might not have their finalize method run.

What is this error?
Is this a problem only with C libraries?
When integrating with a C library is there something I can do to mitigate this?

small reproducible example?

According to this it might be related to the finalizer of an object referring to the same object (or to some dependency chain pointing back at the object), which probably means that the object is resurrected by the finalizer.

I can’t reproduce it with Crystal objects, so it might be an issue with some C lib. The following shows that an object in Crystal can be resurrected only once, since the finalizer is not runnable anymore after the first run.

class A
  getter b : B
  getter count : Int32

  @@count = 0
  
  def initialize(@b : B)
    @@count += 1
    @count = @@count
  end

  def finalize
    puts @count.to_s + " is dead"
    b.a = self
  end
end

class B
  property a : A?
end

b = B.new
a = A.new(b)
a = nil

puts "before GC1"
GC.collect
puts "after GC1"
puts b.a.try(&.count).to_s + " is not dead!"

b.a = nil
puts "before GC2"
GC.collect
puts "after GC2"
puts b.a.nil? ? "dead at last!" : "not dead still 😳"

Output:

before GC1
1 is dead
after GC1
1 is not dead!
before GC2
after GC2
dead at last!
1 Like

Try this:

class A
  property b : B?

  def finalize
    LibC.printf "Bye from A\n"
  end
end

class B
  property a : A?

  def finalize
    LibC.printf "Bye from B\n"
  end
end

loop do
  a = A.new
  b = B.new
  a.b = b
  b.a = a
end

The warning happens when the GC has to call finalize on an object, and that object references other objects that also have finalize and also reference the initial object. The GC doesn’t know which object to finalize first, so it shows that warning.

4 Likes

Oh, so it’s just a regular cycle then? I thought that was resolved by breaking the cycle with some particular ordering (say, take a and kill it to allow b to die too).

Well, maybe? In Expose match's mark verb name by Blacksmoke16 · Pull Request #11324 · crystal-lang/crystal · GitHub removed the warning by removing a finalizer, but I have a hard time seeing how that finalizer triggered a loop. But perhaps it was something else that triggered that warning and the actual content of the finalizer didn’t matter? :thinking:

I don’t think the finalizer contents matter. All that matter is that there’s a cycle, and two objects in that cycle have define a finalize method.

I’m thinking that we could enhance the compiler by actually giving an error if we detect such cycle. We already have a check in place to detect if a struct ends up referencing itself, so it would be a similar check, except that we need to check if, in a cycle, there are two objects that have a finalize method.

In that PR where the warning started to show up, it might have been the case that Regex referenced something that, eventually, held a Regex… but I also couldn’t figure it out from a quick glance.

3 Likes

So this is a personal project I have with about 4k lines of code. I have not been able to simplify this down to a simple code sample. I think this might be to inheriting a finalize method but I am not sure.