ObjectSpace.define_finalizer and memory leaks

i don't think i've ever noticed this behaviour:

cfp:~ > cat a.rb

class C
   def self.count
     c = 0
     ObjectSpace.each_object do |object|
       c += 1 if self === object rescue next
     end
     c
   end
end

loop do
   c = nil ### try with and without this!!!!!!!!!!!!!!

   (2 ** 16).times do
     c = C.new
     object_id = c.object_id
     ObjectSpace.define_finalizer(c){ :nothing }
   end

   puts "before: #{ C.count }"

   GC.start

   puts "after: #{ C.count }"
   puts
end

run both ways. notice that, without the prior declaration of c, the code leaks like crazy: the finalizer itself holds a reference to the object and prevents it being reaped. i don't think i've ever noticed this behavior before. i understand it - but can this be correct? it seems like you should be able to define a finalizer on any object without preventing it from being gc'd!

cheers.

a @ http://drawohara.com/

···

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

The usual way to do finalizers is

class C
  def self.finalize(resource)
    lambda{ resource.free }
  end

  def initialize
    @resource = Resource.new
    ObjectSpace.define_finalizer(self, C.finalize(resource))
  end
end

You can't GC the finalizer proc before running it, and you can't
run it if there's a reference to the finalized object somewhere.
Including the finalizer proc.

···

On 9/25/07, ara.t.howard <ara.t.howard@gmail.com> wrote:

i don't think i've ever noticed this behaviour:

cfp:~ > cat a.rb

class C
   def self.count
     c = 0
     ObjectSpace.each_object do |object|
       c += 1 if self === object rescue next
     end
     c
   end
end

loop do
   c = nil ### try with and without this!!!!!!!!!!!!!!

   (2 ** 16).times do
     c = C.new
     object_id = c.object_id
     ObjectSpace.define_finalizer(c){ :nothing }
   end

   puts "before: #{ C.count }"

   GC.start

   puts "after: #{ C.count }"
   puts
end

run both ways. notice that, without the prior declaration of c, the
code leaks like crazy: the finalizer itself holds a reference to the
object and prevents it being reaped. i don't think i've ever noticed
this behavior before. i understand it - but can this be correct? it
seems like you should be able to define a finalizer on any object
without preventing it from being gc'd!

it seems like you should be able to define a finalizer on any object
without preventing it from being gc'd!

The only way I am aware of is to use methods ...
ObjectSpace.define_finalizer(obj, &method(:my_finalizer))

···

--
Sylvain Joyeux http://www.laas.fr/~sjoyeux

yeah - trying to prevent the closure... smart. i'm not having luck though:

cfp:~ > cat a.rb
class C
   def self.count
     c = 0
     ObjectSpace.each_object do |object|
       c += 1 if self === object rescue next
     end
     c
   end
end

def nothing
end

loop do
   #c = nil ### try with and without this!!!!!!!!!!!!!!

   (2 ** 16).times do
     c = C.new
     ObjectSpace.define_finalizer c, &method(:nothing)
   end

   puts "before: #{ C.count }"

   GC.start

   puts "after: #{ C.count }"
   puts
end

again, if you un-comment the 'c = nil' line it'll work, but avoid the closure alone doesn't work on my platform. does it work for you? did i misunderstand your comment perhaps?

thanks alot for the input.

kind regards.

a @ http://drawohara.com/

···

On Sep 25, 2007, at 10:14 AM, Sylvain Joyeux wrote:

it seems like you should be able to define a finalizer on any object
without preventing it from being gc'd!

The only way I am aware of is to use methods ...
ObjectSpace.define_finalizer(obj, &method(:my_finalizer))
-- Sylvain Joyeux http://www.laas.fr/~sjoyeux

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

working off of you idea of avoiding any new references whatsoever i came up with this, which is working for me, can others confirm?

cfp:~ > cat a.rb
class C
   def self.count
     c = 0
     ObjectSpace.each_object do |object|
       c += 1 if self === object rescue next
     end
     c
   end
end

def finalizer_for object_id
   lambda { p [:finalized, object_id] if $DEBUG }
end

loop do
   (2 ** 16).times do
     c = C.new
     finalizer = finalizer_for c.object_id
     c.instance_eval do
       ObjectSpace.define_finalizer self, &finalizer
     end
   end

   puts "before: #{ C.count }"

   GC.start

   puts "after: #{ C.count }"
   puts
end

thanks for the inspiration!

kind regards.

a @ http://drawohara.com/

···

On Sep 25, 2007, at 10:14 AM, Sylvain Joyeux wrote:

it seems like you should be able to define a finalizer on any object
without preventing it from being gc'd!

The only way I am aware of is to use methods ...
ObjectSpace.define_finalizer(obj, &method(:my_finalizer))
-- Sylvain Joyeux http://www.laas.fr/~sjoyeux

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

Well, I thought I already tried and that it worked fine... But I think I found
the problem ...

define_finalizer does not take any block ! (so, it is normal that it is not
called ;-)) You have to write either the thing Ilmari suggested or remove
the '&' before the method (it works, I just tried it)

···

--
Sylvain