From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-4.1 required=3.0 tests=AWL,BAYES_00, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,SPF_HELO_NONE,SPF_PASS, UNPARSEABLE_RELAY shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id C28CC1F8C6 for ; Thu, 19 Aug 2021 08:04:58 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id C630A120A9D; Thu, 19 Aug 2021 17:03:33 +0900 (JST) Received: from o1678948x4.outbound-mail.sendgrid.net (o1678948x4.outbound-mail.sendgrid.net [167.89.48.4]) by neon.ruby-lang.org (Postfix) with ESMTPS id 7E54F120A8D for ; Thu, 19 Aug 2021 17:03:31 +0900 (JST) Received: by filterdrecv-5d6f795d94-lz8l7 with SMTP id filterdrecv-5d6f795d94-lz8l7-1-611E109B-C 2021-08-19 08:04:43.138135955 +0000 UTC m=+468033.813070002 Received: from herokuapp.com (unknown) by geopod-ismtpd-1-2 (SG) with ESMTP id 3Ziuaq0GSMaQP_BS0WXPyA for ; Thu, 19 Aug 2021 08:04:43.122 +0000 (UTC) Date: Thu, 19 Aug 2021 08:04:43 +0000 (UTC) From: "ioquatix (Samuel Williams)" Message-ID: References: Mime-Version: 1.0 X-Redmine-Project: ruby-master X-Redmine-Issue-Tracker: Feature X-Redmine-Issue-Id: 18083 X-Redmine-Issue-Author: ioquatix X-Redmine-Sender: ioquatix X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-Redmine-MailingListIntegration-Message-Ids: 81177 X-SG-EID: =?us-ascii?Q?RXGrw3WrKfUduNFRrzMMcXYHKEABJI9T84jNjq2g6rBTg=2FXhHIicnPoJUXLutJ?= =?us-ascii?Q?tdNFg7BiEDMUtMCwjaLtb0ItKeRyv3NlZm6AYiJ?= =?us-ascii?Q?s0JX4PcQZ2AWg8FhUFwDlFHpmskdamQKe3Tfho8?= =?us-ascii?Q?8sdOkdYkuu4FWKAAUf+L8WMs4AOYF6yS2td4zFm?= =?us-ascii?Q?KjD5Q21Z0Xhyf7Ls1r+RinxVnxFGl8xaMjVdrVU?= =?us-ascii?Q?fcE05M2oPrF2bN4MgNam7meAf0pWzMf=2Fh+LiLYa?= =?us-ascii?Q?YX05ukxgGaDk860v+dRwQ=3D=3D?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 105001 Subject: [ruby-core:105001] [Ruby master Feature#18083] Capture error in ensure block. X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #18083 has been updated by ioquatix (Samuel Williams). We want to make it easy for people to write robust Ruby programs, even if there is failure. As discussed in there are some tricky edge cases when using `$!` because it's non-local state. As an example: ``` ruby def do_work begin # $! may be set on entry to `begin`. # ... do work ... ensure if $! puts "work failed" else puts "work success" end end end begin raise "Boom" ensure # $! is set. do_work end ``` There are real examples of this kind of code: https://bugs.ruby-lang.org/issues/15567#note-26 There is another kind of error that can occur: ``` ruby begin raise "Boom" rescue => error # Ignore? ensure pp error # pp $! # nil end ``` As a general model, something like the following would be incredibly useful, and help guide people in the right direction: ``` ruby begin ... ensure => error pp "error occurred" if error end ``` Currently you can almost achieve this: ``` ruby begin ... rescue Exception => error # RuboCop will complain. 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 (e.g. care with `rescue ... => error` naming. Also, Rubocop will complain about it, which I think is generally correct since we shouldn't encourage users to `rescue Exception`. Because `$!` can be buggy and encourage incorrect code, I also believe we should consider deprecating it. But in order to do so, we need to provide users with a functional alternative, e.g. `ensure => error`. Another option is to clear `$!` when entering `begin` block (and maybe restore it on exit?), but this may break existing code. #### More Examples ``` Gem: bundler has 1480241823 downloads. /lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb ensure return sleep_time unless $! Gem: rubygems-update has 817517436 downloads. /lib/rubygems/core_ext/kernel_require.rb ensure if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?) STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $! Gem: launchy has 196850833 downloads. /lib/launchy.rb ensure if $! and block_given? then yield $! Gem: spring has 177806450 downloads. /lib/spring/application.rb ensure if $! lib = File.expand_path("..", __FILE__) $!.backtrace.reject! { |line| line.start_with?(lib) } Gem: net-http-persistent has 108254000 downloads. /lib/net/http/persistent.rb ensure return sleep_time unless $! Gem: open4 has 86007490 downloads. /lib/open4.rb ensure killall rescue nil if $! Gem: net-ssh-gateway has 74452292 downloads. /lib/net/ssh/gateway.rb ensure close(local_port) if block || $! Gem: vault has 48165849 downloads. /lib/vault/persistent.rb ensure return sleep_time unless $! Gem: webrick has 46884726 downloads. /lib/webrick/httpauth/htgroup.rb ensure tmp.close if $! Gem: stripe-ruby-mock has 11533268 downloads. /lib/stripe_mock/api/server.rb ensure raise e if $! != e Gem: logstash-input-udp has 8223308 downloads. /lib/logstash/inputs/udp.rb ensure if @udp @udp.close_read rescue ignore_close_and_log($!) @udp.close_write rescue ignore_close_and_log($!) Gem: shrine has 6332950 downloads. /lib/shrine/uploaded_file.rb ensure tempfile.close! if ($! || block_given?) && tempfile ``` Discussion: * nobu: `$!` is thread-local. * ko1: Your proposal is to make shorthand for the following code? * mame: No ```ruby= begin ... ensure error = $! ... end ``` * ko1: FYI ```ruby= def foo p $! #=> # raise SyntaxError rescue SyntaxError end begin raise 'FOO' ensure foo; p $! #=> # exit end ``` * mame: It is surprising to me that `$!` is not a method-local but a thread-local variable ```ruby= at_exit do # |exception| ??? if $! # exception puts "Exiting because of error" end end ``` ```ruby= begin raise "Boom" ensure begin ensure => error # should be nil - yes correct. This is the difference between ko1's workaround and samuel's proposal p $! #=> # error = $! puts "failed" if $! end end ``` * knu: ```ruby= begin Integer("foo") rescue begin Integer("1") ensure => error # You can't tell `$!` came from this begin block without being rescued. p $! #=> # p error #=> nil end end ``` You can almost achieve the ideal situation with this: ``` ruby begin ... rescue Exception => error # RuboCop will complain. raise ensure pp "error occurred" if error end ``` ---------------------------------------- Feature #18083: Capture error in ensure block. https://bugs.ruby-lang.org/issues/18083#change-93400 * 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/