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