0

Here's a snippet

def take_resource
  puts "resource taken"
end

def free_resource source
  puts "resource freed from #{source}"
end

def do_stuff
  tries = 0
  begin
    take_resource
    raise 'oops'
  rescue
    if tries < 3
      tries += 1
      free_resource 'rescue'
      retry
    end
    raise
  ensure
    free_resource 'ensure'
  end
end


do_stuff

# ~> -:13:in `do_stuff': oops (RuntimeError)
# ~>    from -:28:in `<main>'
# >> resource taken
# >> resource freed from rescue
# >> resource taken
# >> resource freed from rescue
# >> resource taken
# >> resource freed from rescue
# >> resource taken
# >> resource freed from ensure

Here we see that ensure clause is not invoked when we retry the block. Why is that? Is there a logical explanation to this? I thought that ensure is called ensure for a reason: it always runs. Well, it turned out that I was wrong.

And while we're on it: do you know about other gotchas in this area (exception handling)?

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • 1
    Hmm? In your code the `ensure` _does_ run, as the last resort. Without thinking very deep about it, that's expected behaviour for me. – steenslag Dec 01 '12 at 11:20
  • @steenslag: it does in the end, yes. But I was expecting it to run on each `retry`. Is it not a logical expectation? – Sergio Tulentsev Dec 01 '12 at 12:00
  • My expectation would be that `ensure` should run when _exiting_ the block, however that occurs. With `retry` control remains in the block, so I’d agree with @steenslag that this is expected behaviour. – matt Dec 01 '12 at 14:24
  • retry starts over at the first line of the begin body. So ensure is only called when rescue raises. Since you are calling retry rescue does not raise and so ensure is not called because the block has not failed. ensure only occurs on exiting the block. – engineersmnky Mar 06 '13 at 21:51

1 Answers1

1

ensure is called when the block is exited, whether via an exception or normally. retry merely transfers the execution point to the start of the block, hence you're still in the block, and ensure isn't called.

Consider that ensure exists to help with cleaning up resources when exiting the block. If you're still in the block, then presumably you're still using the resources.

This is the expected behaviour.

These keywords are described in detail in the Programming Ruby book (http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_exceptions.html)

Alex Korban
  • 14,916
  • 5
  • 44
  • 55
  • Could you also post a link to documentation that covers this? – Sergio Tulentsev Jun 02 '13 at 21:43
  • @SergioTulentsev I added a reference to Programming Ruby. It doesn't explicitly describe the interaction between `retry` and `ensure`, but it does say that the `ensure` clause is executed as the block *terminates*. Obviously, in the case of `retry` the block isn't terminated but continues to execute instead. – Alex Korban Jun 02 '13 at 22:01
  • @AlexKorban: Thanks for spelling this out for me. – Boris Stitnicky Jun 02 '13 at 22:53