Skip to content

Commit

Permalink
We already explicitly save the value of $@ - local()ization is not ne…
Browse files Browse the repository at this point in the history
…eded

511c05c introduced an extension to try{} - unlike in an eval{} a try{}
block has access to the previous value of $@. Since we already have a copy,
we can do a manual restore of $@ instead of the expensive local() mechanism.

Results in a 7% speedup of bare try{} on 5.16
  • Loading branch information
ribasushi committed Jul 4, 2013
1 parent aaf0d61 commit 2b0d579
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 31 deletions.
1 change: 1 addition & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

- fix tests failing on 5.6.x due to differing DESTROY semantics
- excise superfluous local($@) call - 7% speedup

0.12
- doc fixes
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.SKIP
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@
\.gitignore$

MYMETA

maint
57 changes: 26 additions & 31 deletions lib/Try/Tiny.pm
Original file line number Diff line number Diff line change
Expand Up @@ -41,41 +41,35 @@ sub try (&;@) {
}
}

# save the value of $@ so we can set $@ back to it in the beginning of the eval
my $prev_error = $@;

my ( @ret, $error, $failed );

# FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's
# not perfect, but we could provide a list of additional errors for
# $catch->();

{
# localize $@ to prevent clobbering of previous value by a successful
# eval.
local $@;
# 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 = $@;

# failed will be true if the eval dies, because 1 will not be returned
# from the eval body
$failed = not eval {
$@ = $prev_error;

# evaluate the try block in the correct context
if ( $wantarray ) {
@ret = $try->();
} elsif ( defined $wantarray ) {
$ret[0] = $try->();
} else {
$try->();
};

return 1; # properly set $fail to false
my ( @ret, $error );

# failed will be true if the eval dies, because 1 will not be returned
# from the eval body
my $failed = not eval {
$@ = $prev_error;

# evaluate the try block in the correct context
if ( $wantarray ) {
@ret = $try->();
} elsif ( defined $wantarray ) {
$ret[0] = $try->();
} else {
$try->();
};

# copy $@ to $error; when we leave this scope, local $@ will revert $@
# back to its previous value
$error = $@;
}
return 1; # properly set $fail to false
} and $error = $@;

# reset the original value of $@
$@ = $prev_error;

# set up a scope guard to invoke the finally block at the end
my @guards =
Expand Down Expand Up @@ -146,7 +140,7 @@ __END__
=head1 NAME
Try::Tiny - minimal try/catch with proper localization of $@
Try::Tiny - minimal try/catch with proper preservation of $@
=head1 SYNOPSIS
Expand Down Expand Up @@ -329,8 +323,9 @@ More specifically, C<$@> is clobbered at the beginning of the C<eval>, which
also makes it impossible to capture the previous error before you die (for
instance when making exception objects with error stacks).
For this reason C<try> will actually set C<$@> to its previous value (before
the localization) in the beginning of the C<eval> block.
For this reason C<try> will actually set C<$@> to its previous value (the one
available before entering the C<try> block) in the beginning of the C<eval>
block.
=head2 Localizing $@ silently masks errors
Expand Down
14 changes: 14 additions & 0 deletions maint/bench.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env perl

use warnings;
use strict;

use Benchmark::Dumb ':all';
use Try::Tiny;

my $max = 10_000;

cmpthese('0.003', {
eval => sub { do { local $@; eval { die 'foo' } } for (1..$max) },
try => sub { do { try { die 'foo' } } for (1..$max) },
});

0 comments on commit 2b0d579

Please sign in to comment.