hydra/src/script/hydra-notify

80 lines
1.9 KiB
Plaintext
Raw Normal View History

#! /usr/bin/env perl
use strict;
use utf8;
2021-08-16 15:52:14 -04:00
use Getopt::Long;
use Hydra::Event;
use Hydra::Event::BuildFinished;
use Hydra::Helper::AddBuilds;
use Hydra::Helper::Nix;
use Hydra::Plugin;
use Hydra::PostgresListener;
STDERR->autoflush(1);
2020-07-27 16:46:16 -04:00
STDOUT->autoflush(1);
binmode STDERR, ":encoding(utf8)";
2021-02-23 16:10:34 -05:00
my $queued_only;
GetOptions(
"queued-only" => \$queued_only
) or exit 1;
my $config = getHydraConfig();
my $db = Hydra::Model::DB->new();
my @plugins = Hydra::Plugin->instantiate(db => $db, config => $config);
my $dbh = $db->storage->dbh;
my $listener = Hydra::PostgresListener->new($dbh);
$listener->subscribe("build_started");
$listener->subscribe("build_finished");
$listener->subscribe("step_finished");
sub runPluginsForEvent {
my ($event) = @_;
foreach my $plugin (@plugins) {
eval {
$event->execute($db, $plugin);
1;
} or do {
print STDERR "error running $event->{'channel_name'} hooks: $@\n";
}
}
}
# Process builds that finished while hydra-notify wasn't running.
for my $build ($db->resultset('Builds')->search(
{ notificationpendingsince => { '!=', undef } }))
{
print STDERR "sending notifications for build $build->id...\n";
my $event = Hydra::Event::BuildFinished->new($build->id);
runPluginsForEvent($event);
}
2021-02-23 16:10:34 -05:00
# Process incoming notifications.
2021-02-23 16:10:34 -05:00
while (!$queued_only) {
my $messages = $listener->block_for_messages();
while (my $message = $messages->()) {
my $channelName = $message->{"channel"};
my $pid = $message->{"pid"};
my $payload = $message->{"payload"};
eval {
2021-08-16 15:52:25 -04:00
my $event = Hydra::Event->new_event($channelName, $message->{"payload"});
runPluginsForEvent($event);
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 10:56:24 +02:00
1;
} or do {
print STDERR "error processing message '$payload' on channel '$channelName': $@\n";
}
}
}