Just trying to answer the question if it is a bug by making a minimum
version of the leaking version (and correcting Ara's terrible bug how
to write 7
and running it with 1.8 and 1.9
591/92 > cat leak.rb
# vim: sw=2 ts=2 ft=ruby expandtab tw=0 nu syn:
#
class Foo
def initialize
ObjectSpace.define_finalizer self, lambda{}
end
end
(42/6).times {
GC.start
p "Foo" => ObjectSpace.each_object(Foo){}
Foo.new
}
When you create the lambda, what is the value of "self" inside the
lambda?
The answer is that it is going to be the object in which the lambda
was created. In the code above, this would be the object that you are
trying to finalize -- i.e. an instance of Foo. Since the lambda has a
reference to the Foo instance, that instance will always be marked by
the GC, and hence, it will never be garbage collected.
You can verify this by adding a puts statement inside the lambda ...
$ cat a.rb
class Foo
def initialize
ObjectSpace.define_finalizer self, lambda {puts self.object_id}
end
end
10.times {
GC.start
Foo.new
p "Foo" => ObjectSpace.each_object(Foo){}
}
$ ruby a.rb
{"Foo"=>1}
{"Foo"=>2}
{"Foo"=>3}
{"Foo"=>4}
{"Foo"=>5}
{"Foo"=>6}
{"Foo"=>7}
{"Foo"=>8}
{"Foo"=>9}
{"Foo"=>10}
The object ID is never printed; hence, the finalizer is never called.
Now let's define the finalizer lambda outside the scope of the
instance we are trying to finalize. This prevents the lambda from
having a reference to the Foo instance.
$ cat a.rb
Finalizer = lambda do |object_id|
puts object_id
end
class Foo
def initialize
ObjectSpace.define_finalizer self, Finalizer
end
end
10.times {
GC.start
Foo.new
p "Foo" => ObjectSpace.each_object(Foo){}
}
$ ruby a.rb
{"Foo"=>1}
89480
{"Foo"=>1}
{"Foo"=>2}
89480
{"Foo"=>2}
84800
{"Foo"=>2}
89480
{"Foo"=>2}
84800
{"Foo"=>2}
89480
{"Foo"=>2}
84800
{"Foo"=>2}
{"Foo"=>3}
84780
84800
89480
You'll notice that the Foo instance count does not grow (yes, it is
shown as non-zero at the end of the program). But you'll also notice
that the finalizer is called exactly 10 times. Even though the last
Foo instance count shows 3 objects remaining, they are cleaned up as
shown by their object IDs being printed out by our finalizer.
The lesson here is that you always need to create your finalizer Proc
at the Class level, not at the instance level.
The ruby garbage collector is conservative, but it will clean up after
you just fine.
Blessings,
TwP