[ruby-core:105005] [Ruby master Feature#18083] Capture error in ensure block.
From:
"ioquatix (Samuel Williams)" <noreply@...>
Date:
2021-08-19 09:57:40 UTC
List:
ruby-core #105005
Issue #18083 has been updated by ioquatix (Samuel Williams).
@matz I agree with your general feeling completely. But it's too late: We already have "ugly and against the basic concept of ensure" in the form of `$!`. As demonstrated, people are checking `$!` and unfortunately, in general code, it is not safe to check `$!` in an `ensure` block because it might come from the parent scope (or some other scope). This puts us in a difficult situation.
I think the decision depends on whether you want to support or deprecate the usage of `$!`. Fundamentally, if you think that `ensure => error` is ugly, I believe you must agree `ensure ... if $!` is equally ugly.
## Support `$!`
If we want to support $!, I would propose that we make it safe, by limiting it to the current block. That means it's effectively reset on entering a `begin...ensure...end` block, so we prevent it carrying state from the parent scope (if any).
## Deprecate `$!`
If we want to deprecate $!, I would propose that we consider how we want engineers to approach the problem they are currently depending on `$!` for.
The most common use case is something like this:
```ruby
begin
resource = acquire # get a resource from the pool or allocate one if pool is empty.
yield resource
ensure
if $!
free(resource) # resource is broken, get rid of it.
else
release(resource) # resource is okay, return it to the pool.
end
end
```
The proposal to use `else` is a good one, except it doesn't capture all flow controls:
```ruby
begin
resource = acquire # get a resource from the pool or allocate one if pool is empty.
yield resource
rescue Exception
free(resource) # resource is broken, get rid of it.
else
release(resource) # resource is okay, return it to the pool.
end
```
The problem with the above code, is there are some cases where you can exit the block and neither the rescue nor the else block are executed. It will result in a resource leak. Also, some people would argue that `rescue Exception` is wrong. So, can we make the above better? If we make `else` the same as "ensure without exception" then it would be sufficient, but right now it's not.
The next best alternative is something like this:
```ruby
begin
resource = acquire # get a resource from the pool or allocate one if pool is empty.
yield resource
rescue Exception => error
free(resource) # resource is broken, get rid of it.
raise
ensure
unless error
release(resource) # resource is okay, return it to the pool.
end
end
```
So the fundamental problem we have is that we don't have any way to guarantee, AFAIK, on error execute this code, otherwise execute this other code, and it turns out that a lot of code needs this, essentially "ensure if no exception was raised" or "ensure if no rescue block was executed", or something similar.
By the way, your proposed solution also falls short:
~~~
catch(:finished) do
begin
throw :finished
no_error = true
ensure
p :error_handling unless no_error
end
end
~~~
Will execute `error_handling`. This is why it's a non-trivial problem. Everyone comes up with the wrong answer (including me). If you look at real code, you see these incorrect patterns repeated over and over.
----------------------------------------
Feature #18083: Capture error in ensure block.
https://bugs.ruby-lang.org/issues/18083#change-93405
* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
----------------------------------------
As discussed in https://bugs.ruby-lang.org/issues/15567 there are some tricky edge cases.
As a general model, something like the following would be incredibly useful:
``` ruby
begin
...
ensure => error
pp "error occurred" if error
end
```
Currently you can get similar behaviour like this:
``` ruby
begin
...
rescue Exception => error
raise
ensure
pp "error occurred" if error
end
```
The limitation of this approach is it only works if you don't need any other `rescue` clause. Otherwise, it may not work as expected or require extra care. Also, Rubocop will complain about it.
Using `$!` can be buggy if you call some method from `rescue` or `ensure` clause, since it would be set already. It was discussed extensively in https://bugs.ruby-lang.org/issues/15567 if you want more details.
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>