Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finalizer not called before second object is created except when using weakref

I was playing around with ruby finalizers and noticed some behaviour that is very strange to me. I could reduce the triggering code to the following:

require "weakref"

class Foo
    def initialize
        ObjectSpace.define_finalizer(self, self.class.finalize)
    end

    def self.finalize
        proc {
            puts "finalizing"
        }
    end
end

Foo.new # does not work
#WeakRef.new(foo) # Using this instead, everything works as expected
sleep 1
ObjectSpace.garbage_collect
puts "... this did not finalize the object"

Foo.new
ObjectSpace.garbage_collect
puts "but this did?"

As the program says, no finalizer is run before the second call to Foo.new. I tried adding more delay before the first call to the garbage collector (though as I understand, it shouldn't be neccessary at all), but that doesn't do anything.

Strangely enough, if I use the commented-out line i, the first finalizer gets called as I would expect it to be. The second one is still not called before the program exits.

Can anyone explain why this is happening? I am running Ubuntu 12.10 with ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]. I tried reading the weakref code, but as far as I can tell, all it does is storing the objects object_id to retrieve it later.

edit: I understand that manually invoking the garbage collector in a situation like this does not make sense. I'm just trying to understand the mechanics behind this.

like image 314
Simon Kohlmeyer Avatar asked Oct 05 '22 12:10

Simon Kohlmeyer


1 Answers

You can't collect your Foo reference because it is referenced in your finalizer! Thus, because the finalizer itself is holding a reference to the object, the GC never collects it, and thus never triggers the finalizer. You can get around this by just using a WeakRef for the finalizer itself:

require "weakref"

class Foo
  class << self
    attr_accessor :objects_finalized

    def finalize
      proc {
        @objects_finalized ||= 0
        @objects_finalized += 1
      }
    end
  end

  def initialize
    ObjectSpace.define_finalizer WeakRef.new(self), self.class.finalize
  end
end

describe Foo do
  it "should be collected" do
    Foo.new
    expect { GC.start }.to change {
      ObjectSpace.each_object(Foo){} }.from(1).to(0)
  end

  it "should be finalized when it is collected" do
    expect { begin; Foo.new; end; GC.start }.to change {
      Foo.objects_finalized }.from(nil).to(1)
  end
end

With results:

% rspec weakref.rb
..

Finished in 0.03322 seconds
2 examples, 0 failures
like image 117
Chris Heald Avatar answered Oct 10 '22 03:10

Chris Heald