NOTE:This blog had a good run, but is now in retirement.
If you enjoy the content here, please support Gregory's ongoing work on the Practicing Ruby journal.

Writing Block Methods with automatic Resource Cleanup

2009-04-20 19:47, written by Robert Klemme

After we have seen how File.open with a block is safer than without we will look into how such methods are created today.

Ingredients

We need two ingredients:

  1. yield
  2. A begin … end block with ensure

yield is a keyword and invokes the block which is passed to a method. If the caller did not provide a block he’ll earn a LocalJumpError. Result of evaluating yield is the value of the block (remember, we also called them “anonymous functions”).

As you will probably know begin … end blocks can be used to catch exceptions and handle them properly. But this construct has two more features apart from rescue; a full blown block might look like this:

begin
  # do our work
rescue SomeException => e
  # oops!
rescue Exception => e
  # deal with other errors
else
  # good, no exception surfaced!
ensure
  # good or bad, this needs to be done
end

Code in an else clause after rescue is executed when the block is left normally, i.e. without an exception being thrown. Note that in case of an exception it is irrelevant whether it is caught in this begin … end block or not — else will not be executed. Code after ensure is executed under all circumstances — regardless whether an exception is thrown or not. This is the feature that we’ll exploit for our cleanup.

An important thing to know is that the result of the “ensure” code does not affect the block’s result which is normally the value of the last expression evaluated between begin and the first rescue. So anything the cleanup code returns is invisible to the caller (which makes perfect sense if you think about it). Results of rescue and else clauses are retained when they are executed.

Cooking

Assume we have a class that distributes stdin to a set of files much the same as the ‘tee’ command line utility does. This class features a method open which opens files and another method close which closes all file descriptors. Then we can create a class method similar to File.open() which does the automatic cleanup via an ensure section:

class Tee
  def self.open(file_names, mode = "w")
    tee = new(file_names, mode)
    tee.open

    if block_given?
      begin
        yield tee
      ensure
        tee.close
      end
    else
      tee
    end
  end
end

This code does actually work similar to File.open because it acts differently depending on the presence of a block:

  • If there is no block, then the “tee” is created and opened. It is the return value of the method.
  • If there is a block, then that is called with the opened “tee” instance and after termination this is closed.

That’s really all there is to it. A final remark: in order to ensure that this pattern works properly you need to make sure the initialization is done before the begin. Otherwise any exceptions thrown during initialization will trigger the cleanup code which then will act on an incomplete or completely different object altogether (chances are that you will get NoMethodError from nil in this case).

You can also look at the full code if you want to see the complete story.

blog comments powered by Disqus