The Trouble with Ruby Finalizers

2010-02-24

I was test driving Devil, the developer’s image library, recently to see if it would work for us in a long-living daemon. Task #1 to that end is to verify the absence of memory leaks, which seem to be common in image libraries. It was almost immediately apparent that Devil contained a large memory leak. So I worked with John Mair to fix the issue.

Devil has a Devil::Image class which uses a finalizer to delete native resources when the image is garbage collected. The problem is that Ruby finalizers are notoriously difficult to use properly so often times they aren’t actually run. Here’s why:

class Devil::Image
    attr_reader :name, :file

    def initialize(name, file)
        @name = name
        @file = file

        ObjectSpace.define_finalizer( self, proc { IL.DeleteImages(1, [name]) } )
    end
end

So what’s wrong with this code? The issue is that the finalizer proc is a closure which holds a reference to it’s self, thus making it impossible for the image object to ever be garbage collected. When creating a finalizer proc, you should always use a class method to create the proc so that it does not hold a reference to the corresponding instance, like so:

def initialize(name, file)
      @name = name
      @file = file

      ObjectSpace.define_finalizer( self, self.class.finalize(name) )
  end
  def self.finalize(name)
    proc { IL.DeleteImages(1, [name]) }
  end

A subtle and evil bug, just like its namesake!