Today I was thinking about retry support in JRuby, and figured we've reached a decision point.
retry is a tricky little bugger. It works the way it does in MRI largely because of the way the interpreter works; so in that sense it's very implementation specific. So here's some example code, see if you can figure out what it prints.
$count = 0
class Foo
def initialize
$count += 1
@value = $count
end
def do_retry(i)
puts "value is #{@value} and i is #{i}"
retry
end
end
a = 0
Foo.new.do_retry(a += 1) {}
Give up? Here's the answer:
value is 1 and i is 1
value is 2 and i is 2
value is 3 and i is 3
The trick here is the retry. A retry can only be used in a rescue block (which is ok), inside a block, or inside a method that's been passed a block. Excluding rescue, retry causes the original call to the method with the block to reevaluate. And I don't just mean re-execute with the same values, I mean re-evaluate both the receiver and the arguments and make the call again.
So for example, this method invocation
def foo(a); puts 3; retry; end
(print 1; self).foo((print 2; nil)) {}
Prints out "123" forever, because the receiver (the (print 1; self) bit) and the arguments (the (print 2; nil) bit) get executed again. Goofy, right?
This also affects retry within a block
def foo(a); puts 3; yield; end
(print 1; self).foo((print 2; nil)) { retry }
This also prints "123" repeatedly.
The problem I've run into is that it's really cumbersome to implement this correctly in the compiler, cumbersome enough I'm debating whether we'll support it. There's a few reasons for this:
- retry is handled using exception; so every method call that takes a block would have to be wrapped with exception-handling for retry to work. Exception-handling is expensive, even if no exceptions are actually raised. It would also add a significant amount of code.
- there's no way to know that a method will be reevaluated by looking at it, which makes it both dangerous and impossible to predict.
- nobody understands retry, and nobody uses it.
As I understand it, retry in a method body is going away in 1.9, so it should be considered deprecated behavior. So far, retry in a block is not yet going away, though I'd prefer it did. Actually, I wish this behavior never existed, because it makes a lot of assumptions about the way Ruby is implemented. Ask if you want to know more about that.
I'm looking for input on this. If there's a way to implement it I have missed, please tell me. If you have a concern with this feature being disabled, please tell me (it won't affect retry within rescue blocks). If you want to talk about it more, please tell me.
- Charlie