The Trouble with Ruby Finalizers

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]) } )

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) )
  def self.finalize(name)
    proc { IL.DeleteImages(1, [name]) }

A subtle and evil bug, just like its namesake!

11 thoughts on “The Trouble with Ruby Finalizers”

  1. Agreed. The OS#define_finalizer API sadly invites this exact bug. Pretty much everyone who has even used OS#define_finalizer has hit this bug. Some catch it right away, but lots don’t.

    The API invites this bug because there the object isn’t passed to the block, and thusly people think they want the object to be in the closure to they can do something with it when it’s being finalized. Ruby’s GC can’t deal with this, and thus the passing of #object_id to the block.

    In Rubinius, I’m working on adding true finalizers: code that runs when an object was just detected as garbage, but can access the object itself. The finalizer code can even resurrect the object and it will be finalized later. Not yet sure what the API for this will be (a #finalize method on the object maybe?) but it should help in some ways.

  2. Hey, i’m tying to figure out finalizers and came across this post but can’t get your example to work. There is a extraneous right curly brace in the line

    ObjectSpace.define_finalizer( self, self.class.finalize(name) } )

    and even after I remove that, the syntax doesn’t define a callable method. Instead it just calls the method :)

    You can wrap it in a Proc

    ObjectSpace.define_finalizer(self,proc{|id| self.class.finalize(name)})

    but that creates a closure which binds self, preventing the object from being garbage collected!

    How on earth are you meant to get access to the member variables from a finaliser?

  3. Jamie, I’ve fixed the typo. You want it to call finalize; that method returns the actual proc that will be called to finalize the object.

    If you want access to the member variables, you have to pass them into the finalize() method so they are available to the finalizer, just like the name variable.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>