hydra/src
Nikola Knezevic f79810bac1 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?
2020-05-26 11:19:43 +02:00
..
hydra-eval-jobs Fix build 2020-05-12 16:14:20 +02:00
hydra-evaluator Merge pull request #730 from NixOS/flake 2020-04-07 11:18:38 +02:00
hydra-queue-runner hydra-queue-runner: don't try to distribute builds on localhost 2020-05-03 00:05:52 -04:00
lib Improve handling of Perl's block eval errors 2020-05-26 11:19:43 +02:00
libhydra Build against nix-master 2020-02-20 10:24:04 +01:00
root Render the jobset page correctly when there are fetch errors 2020-05-20 17:47:18 +02:00
script Improve handling of Perl's block eval errors 2020-05-26 11:19:43 +02:00
sql Add missing SQL upgrade script for NOT NULL on type 2020-05-18 10:59:55 +02:00
ttf Add font for the captcha 2013-03-04 12:16:13 +01:00
Makefile.am Revert "hydra-eval-jobs -> nix eval-hydra-jobs" 2020-02-19 20:36:52 +01:00
Makefile.PL * Move everything up one directory. 2009-03-05 13:41:57 +00:00