Issue #15567 has been updated by jeremyevans0 (Jeremy Evans).


Eregon (Benoit Daloze) wrote in #note-21:
> Thanks for the link.
> 
> Is `throw` used for other things than `redirect`?

Yes, it's used anytime you want to return a response immediately.  I just used redirect as an example.

> If `redirect` would use a regular exception, and for instance pass `[]` as the backtrace, it would probably be as efficient, but then gives the possibility to differentiate for example with `rescue => e; e&.should_commit?` or so.
> `throw`/`catch` has AFAIK no way to communicate any information, so there is no way to know if the `throw` is meant as a convenience return like for `redirect` or as some kind of bailout which should not commit the transaction.
> It can't even be differentiated from return/break so it seems a bad idea to use `throw` in the first place.

Changing a throw (which doesn't indicate error) to an exception (which indicates error) for a case that is not an error makes no sense to me.  The problem is that Timeout uses throw instead of an exception for error cases (timeouts), not code that uses throw for non-error cases.  

> It feels weird that Timeout uses `throw` and not `raise`, and it seems counter-intuitive (everyone knows `Timeout.timeout {}` raises Timeout::Error, and exception, `throw` is unexpected)
> It also adds quite some complexity: https://github.com/ruby/timeout/blob/4893cde0eda321448a1a86487ac9b571f6c35727/lib/timeout.rb#L29-L50
> Does anyone know what's the point of that?

I believe this is because exception handling inside the Timeout.timeout block doesn't interfere with timeout handling.

> What I could find is https://github.com/ruby/timeout/commit/238c003c921e6e555760f8e96968562a622a99c4
> and https://bugs.ruby-lang.org/issues/8730

Yep, that pretty much confirms it.  I don't agree with the tradeoff.  Using throw for error cases is wrong, IMO.  It would be better to have cases where Timeout didn't work due to improper rescuing, than not being able to use throw correctly because of Timeout's implementation.  There are already places where Timeout doesn't work correctly, after all. A timeout error in my mind is more similar to a TERM signal, and Ruby handles that using an exception.

I should point out that this is not just a throw issue, it occurs for any non local exit.  Here's an example using return.  We have to use a transaction around the initial retrieval of the record due to the use of FOR UPDATE, because in certain conditions we want to update the record later, and don't want any modifications to the record in between.  We need the transaction to commit and not rollback on exit, otherwise we lose the update to the associated object.  

```ruby
def foo
  record = nil

  transaction do
    return :a unless record  = dataset.for_update.first

    record.associated_object.update()

    return :b if record.some_condition?
 
    record.update() if record.some_other_condition?
  end
  
  record
end
```

While you can generally rewrite code to avoid non-local exits like this, often the non-local exit approach is simplest.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-92322

* Author: ioquatix (Samuel Williams)
* Status: Rejected
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
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>