From: merch-redmine@... Date: 2021-06-02T17:39:59+00:00 Subject: [ruby-core:104150] [Ruby master Feature#15567] Allow ensure to match specific situations 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: