TIL Ruby ObjectSpace#define_finalizer

I ran into a situation recently where I was writing a library that utilized large files. Specifically a GTFS feed parser. I wanted to cache the files away someplace so I could safely access them for the life of an object, but I didn’t want to leave them hanging around afterwards. I couldn’t do all my work inside a block passed to Dir.mktmpdir because I was planning on accessing the cache when necessary (not just one big up front parsing operation).

This got me wondering: “does Ruby have finalizers?”. It turns out it does in the form of ObjectSpace#define_finalizer. It’s worth mentioning that there are some tricks to using them which are nicely documented on Mike Perham’s blog here

The gist of it is this: don’t create finalizers which hold a reference to the instance they’re finalizing or it just won’t work. I’m glad I found that blog post before I started coding because I would have certainly fallen into the trap he describes. It probably doesn’t help that the ruby doc examples don’t include any examples of adding a finalizer in a class definition.

So all of this works out pretty nicely and leaves me with code looking something like this:

1
2
3
4
5
6
7
8
9
10
11
class Source
  def initialize(source_zip)
    @tmp_dir = Dir.mktmpdir
    ObjectSpace.define_finalizer(self, self.class.finalize(@tmp_dir))
    # Dump contents of zip into tmp dir
  end

  def self.finalize(tmp_dir)
    proc {FileUtils.rm_rf(tmp_dir)}
  end
end