Circular References: How to prove that they are garbage collected

Hello Ruby Fans

I'm converting a perl example which deals with the circular data structures, and how to make sure they are destroyed properly.
(For the pleac cookbook, pleac.sourceforge.net)

I have read several times that the garbage collector has no problems with circular references, so I tried to prove it.
But with my ruby 1.8.0 it looks like the garbage collector
does not destroys the Node A, B and C, but only the lonely node:

GC.enable

class RingNode
   attr_accessor :next
   attr_accessor :prev
   attr_reader :name

   def initialize(aName)
       @name = aName
       ObjectSpace.define_finalizer(self,
             self.class.method(:finalize).to_proc)
   end

   def RingNode.finalize(id)
       puts "Node #{id} dying"
   end

   def RingNode.show_all_objects
       ObjectSpace.each_object {|id|
           puts id.name if id.class == RingNode
       }
   end
end

a = RingNode.new("Node A")
b = RingNode.new("Node B")
c = RingNode.new("Node C")
a.next = b
b.next = c
c.next = a
a.prev = c
c.prev = b
b.prev = a
lonely = RingNode.new("Lonely node")

a = nil
b = nil
c = nil
lonely = nil

GC.start
puts "Garbage Collection called"
RingNode.show_all_objects
puts "End of Program"

What happens is that Noda A,B,C get destroyed, but after the end of the program. If I comment out the finalizer (in case I made an error with it) and only use the ObjectSpace.each_object, I see also Node A,B,C after the garbage collection.

Karsten Meier wrote:

Hello Ruby Fans

I'm converting a perl example which deals with the circular data structures, and how to make sure they are destroyed properly.
(For the pleac cookbook, pleac.sourceforge.net)

I have read several times that the garbage collector has no problems with circular references, so I tried to prove it.
But with my ruby 1.8.0 it looks like the garbage collector
does not destroys the Node A, B and C, but only the lonely node:

"Only the lonely
  Node the way I feel tonight"

   ... hehe. Sorry Roy Orbison.

Anyway, try it with the following 3 additional lines. All four nodes print their message before the "Garbage colection called" message. I think what's going on is conservatism. Keeping the local vars in a method makes sure that stack frame is gone begore the GC starts.

GC.enable

class RingNode
  attr_accessor :next
  attr_accessor :prev
  attr_reader :name

  def initialize(aName)
      @name = aName
      ObjectSpace.define_finalizer(self,
            self.class.method(:finalize).to_proc)
  end

  def RingNode.finalize(id)
      puts "Node #{id} dying"
  end

  def RingNode.show_all_objects
      ObjectSpace.each_object {|id|
          puts id.name if id.class == RingNode
      }
  end
end

def main

a = RingNode.new("Node A")
b = RingNode.new("Node B")
c = RingNode.new("Node C")
a.next = b
b.next = c
c.next = a
a.prev = c
c.prev = b
b.prev = a
lonely = RingNode.new("Lonely node")

end

a = nil
b = nil
c = nil
lonely = nil

main

···

GC.start
puts "Garbage Collection called"
RingNode.show_all_objects
puts "End of Program"

What happens is that Noda A,B,C get destroyed, but after the end of the program. If I comment out the finalizer (in case I made an error with it) and only use the ObjectSpace.each_object, I see also Node A,B,C after the garbage collection.

Moin!

The problems is with the finalizer. It holds a reference to the object AFAIK.

Maybe you could just capture the .object_id and use ObjectSpace._id2ref to check whether the Objects still exist after you ran the GC?

Oh, or you could use a WeakRef.

Regards,
Florian Gross

Oops... I put the end in the wrong place. It should be like this:

def main
   a = RingNode.new("Node A")
   b = RingNode.new("Node B")
   c = RingNode.new("Node C")
   a.next = b
   b.next = c
   c.next = a
   a.prev = c
   c.prev = b
   b.prev = a
   lonely = RingNode.new("Lonely node")

   a = nil
   b = nil
   c = nil
   lonely = nil
end

main
GC.start
...

Thanks, this is doing the trick.
Even when I put the assignments to nil also in the function
(just to be sure that there is has nothing to do with C++ style
destruction of objects on the stack when leaving the function)

Joel VanderWerf wrote:

···

Karsten Meier wrote:

Hello Ruby Fans

I'm converting a perl example which deals with the circular data structures, and how to make sure they are destroyed properly.
(For the pleac cookbook, pleac.sourceforge.net)

I have read several times that the garbage collector has no problems with circular references, so I tried to prove it.
But with my ruby 1.8.0 it looks like the garbage collector
does not destroys the Node A, B and C, but only the lonely node:

"Only the lonely
Node the way I feel tonight"

  ... hehe. Sorry Roy Orbison.

Anyway, try it with the following 3 additional lines. All four nodes print their message before the "Garbage colection called" message. I think what's going on is conservatism. Keeping the local vars in a method makes sure that stack frame is gone begore the GC starts.

GC.enable

class RingNode
  attr_accessor :next
  attr_accessor :prev
  attr_reader :name

  def initialize(aName)
      @name = aName
      ObjectSpace.define_finalizer(self,
            self.class.method(:finalize).to_proc)
  end

  def RingNode.finalize(id)
      puts "Node #{id} dying"
  end

  def RingNode.show_all_objects
      ObjectSpace.each_object {|id|
          puts id.name if id.class == RingNode
      }
  end
end

def main

a = RingNode.new("Node A")
b = RingNode.new("Node B")
c = RingNode.new("Node C")
a.next = b
b.next = c
c.next = a
a.prev = c
c.prev = b
b.prev = a
lonely = RingNode.new("Lonely node")

end

a = nil
b = nil
c = nil
lonely = nil

main

GC.start
puts "Garbage Collection called"
RingNode.show_all_objects
puts "End of Program"

What happens is that Noda A,B,C get destroyed, but after the end of the program. If I comment out the finalizer (in case I made an error with it) and only use the ObjectSpace.each_object, I see also Node A,B,C after the garbage collection.