Improve handling of Perl's block eval errors
Taken from `Perl::Critic`: A common idiom in perl for dealing with possible errors is to use `eval` followed by a check of `$@`/`$EVAL_ERROR`: eval { ... }; if ($EVAL_ERROR) { ... } There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change between the end of the `eval` and the `if` statement. The issue are object destructors: package Foo; ... sub DESTROY { ... eval { ... }; ... } package main; eval { my $foo = Foo->new(); ... }; if ($EVAL_ERROR) { ... } Assuming there are no other references to `$foo` created, when the `eval` block in `main` is exited, `Foo::DESTROY()` will be invoked, regardless of whether the `eval` finished normally or not. If the `eval` in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then `$EVAL_ERROR` will be empty by the time that the `if` is executed. Additional issues arise if you depend upon the exact contents of `$EVAL_ERROR` and both `eval`s fail, because the messages from both will be concatenated. Even if there isn't an `eval` directly in the `DESTROY()` method code, it may invoke code that does use `eval` or otherwise affects `$EVAL_ERROR`. The solution is to ensure that, upon normal exit, an `eval` returns a true value and to test that value: # Constructors are no problem. my $object = eval { Class->new() }; # To cover the possiblity that an operation may correctly return a # false value, end the block with "1": if ( eval { something(); 1 } ) { ... } eval { ... 1; } or do { # Error handling here }; Unfortunately, you can't use the `defined` function to test the result; `eval` returns an empty string on failure. Various modules have been written to take some of the pain out of properly localizing and checking `$@`/`$EVAL_ERROR`. For example: use Try::Tiny; try { ... } catch { # Error handling here; # The exception is in $_/$ARG, not $@/$EVAL_ERROR. }; # Note semicolon. "But we don't use DESTROY() anywhere in our code!" you say. That may be the case, but do any of the third-party modules you use have them? What about any you may use in the future or updated versions of the ones you already use?
This commit is contained in:
@ -29,9 +29,11 @@ sub buildStarted {
|
||||
or die "build $buildId does not exist\n";
|
||||
|
||||
foreach my $plugin (@plugins) {
|
||||
eval { $plugin->buildStarted($build); };
|
||||
if ($@) {
|
||||
print STDERR "$plugin->buildStarted: $@\n";
|
||||
eval {
|
||||
$plugin->buildStarted($build);
|
||||
1;
|
||||
} or do {
|
||||
print STDERR "error with $plugin->buildStarted: $@\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,9 +55,11 @@ sub buildFinished {
|
||||
}
|
||||
|
||||
foreach my $plugin (@plugins) {
|
||||
eval { $plugin->buildFinished($build, [@dependents]); };
|
||||
if ($@) {
|
||||
print STDERR "$plugin->buildFinished: $@\n";
|
||||
eval {
|
||||
$plugin->buildFinished($build, [@dependents]);
|
||||
1;
|
||||
} or do {
|
||||
print STDERR "error with $plugin->buildFinished: $@\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,9 +78,11 @@ sub stepFinished {
|
||||
$logPath = undef if $logPath eq "-";
|
||||
|
||||
foreach my $plugin (@plugins) {
|
||||
eval { $plugin->stepFinished($step, $logPath); };
|
||||
if ($@) {
|
||||
print STDERR "$plugin->stepFinished: $@\n";
|
||||
eval {
|
||||
$plugin->stepFinished($step, $logPath);
|
||||
1;
|
||||
} or do {
|
||||
print STDERR "error with $plugin->stepFinished: $@\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,8 +121,8 @@ while (1) {
|
||||
} elsif ($channelName eq "step_finished") {
|
||||
stepFinished(int($payload[0]), int($payload[1]));
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
1;
|
||||
} or do {
|
||||
print STDERR "error processing message '$payload' on channel '$channelName': $@\n";
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user