always run finally blocks no matter how we leave the try block (RT #112099)
Lukas Mai [Fri, 19 Feb 2016 17:24:53 +0000 (18:24 +0100)]
lib/Try/Tiny.pm
t/finally.t
t/global_destruction_forked.t

index 7c97b62..90963b0 100644 (file)
@@ -29,6 +29,8 @@ BEGIN {
   *_HAS_SUBNAME = ($su || $sn) ? sub(){1} : sub(){0};
 }
 
+our @_finally_guards;
+
 # Need to prototype as @ not $$ because of the way Perl evaluates the prototype.
 # Keeping it at $$ means you only ever get 1 sub because we need to eval in a list
 # context & not a scalar one
@@ -72,6 +74,13 @@ sub try (&;@) {
   _subname("${caller}::try {...} " => $try)
     if _HAS_SUBNAME;
 
+  # set up scope guards to invoke the finally blocks at the end.
+  # this should really be a lexical variable instead of our/local but that
+  # causes issues with perls < 5.20 due to perl rt#119311
+  local @_finally_guards =
+    map { Try::Tiny::ScopeGuard->_new($_) }
+    @finally;
+
   # save the value of $@ so we can set $@ back to it in the beginning of the eval
   # and restore $@ after the eval finishes
   my $prev_error = $@;
@@ -99,14 +108,12 @@ sub try (&;@) {
   $error = $@;
   $@ = $prev_error;
 
-  # set up a scope guard to invoke the finally block at the end
-  my @guards =
-    map { Try::Tiny::ScopeGuard->_new($_, $failed ? $error : ()) }
-    @finally;
-
   # at this point $failed contains a true value if the eval died, even if some
   # destructor overwrote $@ as the eval was unwinding.
   if ( $failed ) {
+    # pass $error to the finally blocks
+    push @$_, $error for @_finally_guards;
+
     # if we got an error, invoke the catch block.
     if ( $catch ) {
       # This works like given($error), but is backwards compatible and
index 2624cc9..a13c9a8 100644 (file)
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 27;
+use Test::More tests => 30;
 
 use Try::Tiny;
 
@@ -128,4 +128,16 @@ is($_, "foo", "same afterwards");
   like $originals[1], qr/fin 3 at/, 'Second warning contains original exception';
 }
 
-1;
+{
+  my $finally;
+  SKIP: {
+    try {
+      pass('before skip in try');
+      skip 'whee', 1;
+      fail('not reached');
+    } finally {
+      $finally = 1;
+    };
+  }
+  ok $finally, 'finally ran';
+}
index a533000..05c35e9 100644 (file)
@@ -18,15 +18,15 @@ use Try::Tiny;
   package WithFinally;
   use Try::Tiny;
 
+  our $_in_destroy;
   sub DESTROY {
+    local $_in_destroy = 1;
     try {}
     finally {};
     return;
   }
 }
 
-my $parent = $$;
-
 try {
   my $pid = fork;
   unless ($pid) {
@@ -52,6 +52,6 @@ try {
   waitpid $pid, 0;
   is $?, 0, 'nested try in cleanup after fork does not maintain outer finally block';
 }
-finally { exit 1 if $parent != $$ };
+finally { exit 1 if $WithFinally::_in_destroy };
 
 pass("Didn't just exit");