Misc #19740
closedBlock taking methods can't differentiate between a non-local return and a throw
Description
Opening this as Misc, as at this stage I don't have a fully formed feature request.
Ref: https://github.com/ruby/ruby/commit/1a3bcf103c582b20e9ea70dfed0ee68b24243f55
Ref: https://github.com/ruby/timeout/pull/30
Ref: https://github.com/rails/rails/pull/29333
Context¶
Rails has this problem in the Active Record transaction API.
The way it works is that it yields to a block, and if no error was raised the SQL transaction is committed, otherwise it's rolled back:
User.transaction do
do_thing
end # COMMIT
or
User.transaction do
raise SomeError
end # ROLLBACK
The problem is that there are more ways a method can be exited:
User.transaction do
return # non-local exit
end
User.transaction do
throw :something
end
In the case of a non-local return, we'd want to commit the transaction, but in the case of a throw, particularly since it's internally used by Timeout.timeout
since Ruby 2.1, we'd rather consider that an error and rollback.
But as far as I'm aware, there is not way to distinguish the two cases.
def transaction
returned = false
yield
returned = true
ensure
if $!
# error was raised
elsif returned
# no uniwnd
else
# non-local return or throw, don't know
end
end
I think it could be useful to have a way to access the currently thrown object, similar to $!
for such cases, or some other way to tell what is going on.
There is some discussion going on in https://github.com/ruby/timeout/pull/30 about whether Timeout
should throw or raise, and that may solve part of the problem, but regardless of where this leads, I think being able to check if something is being thrown would be valuable.
cc @matthewd (Matthew Draper)
FYI @jeremyevans0 (Jeremy Evans) @Eregon (Benoit Daloze)