Turn hydra-notify into a daemon
[?]
Aug 9, 2019, 5:11 PM
32KJOERMPFWZZZCIN6TGGVX72GMAJT6VCCIP7S65EHNF3KM42KWACDependencies
- [2]
FHVJYJFEUpload build logs to the binary cache - [3]
SMUWBAD5Send BuildFinished notifications on cached build results. - [4]
PXTSKX4GAdd buildQueued plugin hook - [5]
ZUXMEUX6Improve erorr message - [6]
HZZCYIYThydra-queue-runner: Allow concurrent notifications - [7]
B7ENVLRShydra-queue-runner: Make build notification more reliable - [8]
LSUX6IQRUpdate to latest nixUnstable - [9]
7Q2PXRZAhydra-notify step-finished: Don't barf if the step has no log file - [10]
7LD275CWallow using a shorter context and increase hydra-notify debug - [11]
FCTX433OAdd buildStarted plugin hook - [12]
YTAYNN7VQueue monitor: Bail out earlier if a step has failed previously - [13]
EPWEMRI2Allow determinism checking for entire jobsets - [14]
6WRGCITDEnable declarative projects. - [15]
5AIYUMTBBasic remote building - [16]
XV4AEKJChydra-queue-runner: Handle status queries on the main thread - [17]
UYUVQWXQFix hydra-queue-runner --build-one - [18]
HJOEIMLRRefactor - [19]
NTEDD7T4Provide a plugin hook for when build steps finish - [20]
T2EIYJNGOn SIGINT, shut down the builder threads - [21]
X4KYZJBQUse latest nixUnstable - [22]
B2L4T3X6Sync with Nix - [23]
UVQJBDHNMove log compression to a plugin - [24]
OG3Z3QGCNamespace cleanup - [25]
IE2PRAQUhydra-queue-runner: Send build notifications - [26]
BCDHO4OUSet propagatedFrom for cached failed build steps - [27]
X6FOUYFJint2String -> std::to_string - [28]
K5G5GZY7Guard against concurrent invocations of hydra-queue-runner - [29]
RNJILKTWUpload log files to the right location - [30]
MHVIT4JYSplit hydra-queue-runner.cc more - [31]
OKQLN5AGSet proper charset on log files - [32]
SL3WSRAChydra-queue-runner: Limit memory usage - [33]
LVQXQIYAKill active build steps when builds are cancelled - [34]
JPHDKOMJhydra-queue-runner: Keep some notification statistics - [35]
HHOMBU7Ghydra-queue-runner: Implement timeouts - [36]
KQ3EGUQYAdd some instrumentation to keep track of dispatcher cost - [37]
7LWB2J2ZPeriodically clear orphaned build steps - [38]
C6HOMHZWDon't try to handle SIGINT - [39]
GS4BE6TBAsynchronously compress build logs - [40]
BRAESISHWarn if PostgreSQL appears stalled - [*]
24BMQDZAStart of single-process hydra-queue-runner
Change contents
- edit in src/hydra-queue-runner/builder.cc at line 101
auto conn(dbPool.get()); - replacement in src/hydra-queue-runner/builder.cc at line 127
build = build2;enqueueNotificationItem({NotificationItem::Type::BuildStarted, build->id});build = build2;pqxx::work txn(*conn);notifyBuildStarted(txn, build->id);txn.commit(); - edit in src/hydra-queue-runner/builder.cc at line 151
auto conn(dbPool.get()); - edit in src/hydra-queue-runner/builder.cc at line 175
/* Asynchronously run plugins. FIXME: if we're killed,plugin actions might not be run. Need to ensureat-least-once semantics. */enqueueNotificationItem({NotificationItem::Type::StepFinished, buildId, {}, stepNr, result.logFile}); - replacement in src/hydra-queue-runner/builder.cc at line 342
for (auto id : buildIDs)enqueueNotificationItem({NotificationItem::Type::BuildFinished, id});{pqxx::work txn(*conn);for (auto id : buildIDs)notifyBuildFinished(txn, id, {});txn.commit();} - replacement in src/hydra-queue-runner/builder.cc at line 466
auto notificationSenderQueue_(notificationSenderQueue.lock());notificationSenderQueue_->push(NotificationItem{NotificationItem::Type::BuildFinished, buildId, dependentIDs});pqxx::work txn(*conn);notifyBuildFinished(txn, buildId, dependentIDs);txn.commit(); - edit in src/hydra-queue-runner/builder.cc at line 470
notificationSenderWakeup.notify_one(); - edit in src/hydra-queue-runner/hydra-queue-runner.cc at line 302[11.2024][42.13421]
assert(result.logFile.find('\'') == std::string::npos);txn.exec(fmt("notify step_finished, '%d %d %s'", buildId, stepNr,result.logFile.empty() ? "-" : result.logFile)); - replacement in src/hydra-queue-runner/hydra-queue-runner.cc at line 456
void State::notificationSender()void State::notifyBuildStarted(pqxx::work & txn, BuildID buildId) - replacement in src/hydra-queue-runner/hydra-queue-runner.cc at line 458[11.1383]→[11.1383:1416](∅→∅),[11.1416]→[11.170:171](∅→∅),[11.170]→[11.170:171](∅→∅),[11.171]→[11.1417:1801](∅→∅),[11.1801]→[11.0:1](∅→∅),[11.1]→[8.512:578](∅→∅),[11.58]→[11.1801:1802](∅→∅),[8.578]→[11.1801:1802](∅→∅),[11.1801]→[11.1801:1802](∅→∅),[11.1802]→[11.653:744](∅→∅),[11.744]→[11.1896:1897](∅→∅),[11.1896]→[11.1896:1897](∅→∅),[11.1897]→[11.59:118](∅→∅),[11.118]→[11.1897:1940](∅→∅),[11.1897]→[11.1897:1940](∅→∅),[11.1940]→[11.1732:2662](∅→∅),[11.2662]→[10.0:94](∅→∅),[10.94]→[11.2117:2300](∅→∅),[11.212]→[11.2117:2300](∅→∅),[11.2662]→[11.2117:2300](∅→∅),[11.2117]→[11.2117:2300](∅→∅),[11.2300]→[11.184:218](∅→∅),[11.218]→[11.2338:2339](∅→∅),[11.2338]→[11.2338:2339](∅→∅),[11.2339]→[5.0:133](∅→∅),[5.133]→[11.2505:2506](∅→∅),[11.990]→[11.2505:2506](∅→∅),[11.2505]→[11.2505:2506](∅→∅),[11.2506]→[11.119:178](∅→∅),[11.178]→[7.396:771](∅→∅)
while (true) {try {NotificationItem item;{auto notificationSenderQueue_(notificationSenderQueue.lock());while (notificationSenderQueue_->empty())notificationSenderQueue_.wait(notificationSenderWakeup);item = notificationSenderQueue_->front();notificationSenderQueue_->pop();}MaintainCount<counter> mc(nrNotificationsInProgress);printMsg(lvlChatty, format("sending notification about build %1%") % item.id);auto now1 = std::chrono::steady_clock::now();Pid pid = startProcess([&]() {Strings argv;switch (item.type) {case NotificationItem::Type::BuildStarted:argv = {"hydra-notify", "build-started", std::to_string(item.id)};for (auto id : item.dependentIds)argv.push_back(std::to_string(id));break;case NotificationItem::Type::BuildFinished:argv = {"hydra-notify", "build-finished", std::to_string(item.id)};for (auto id : item.dependentIds)argv.push_back(std::to_string(id));break;case NotificationItem::Type::StepFinished:argv = {"hydra-notify", "step-finished", std::to_string(item.id), std::to_string(item.stepNr), item.logPath};break;};printMsg(lvlChatty, "Executing hydra-notify " + concatStringsSep(" ", argv));execvp("hydra-notify", (char * *) stringsToCharPtrs(argv).data()); // FIXME: remove castthrow SysError("cannot start hydra-notify");});int res = pid.wait();if (!statusOk(res))throw Error("notification about build %d failed: %s", item.id, statusToString(res));auto now2 = std::chrono::steady_clock::now();if (item.type == NotificationItem::Type::BuildFinished) {auto conn(dbPool.get());pqxx::work txn(*conn);txn.parameterized("update Builds set notificationPendingSince = null where id = $1")(item.id).exec();txn.commit();}txn.exec(fmt("notify build_started, '%s'", buildId));} - edit in src/hydra-queue-runner/hydra-queue-runner.cc at line 461
nrNotificationTimeMs += std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1).count();nrNotificationsDone++; - replacement in src/hydra-queue-runner/hydra-queue-runner.cc at line 462[11.326]→[11.2506:2545](∅→∅),[11.2506]→[11.2506:2545](∅→∅),[11.2545]→[11.327:364](∅→∅),[11.364]→[11.2545:2662](∅→∅),[11.2545]→[11.2545:2662](∅→∅)
} catch (std::exception & e) {nrNotificationsFailed++;printMsg(lvlError, format("notification sender: %1%") % e.what());sleep(5);}}void State::notifyBuildFinished(pqxx::work & txn, BuildID buildId,const std::vector<BuildID> & dependentIds){auto payload = fmt("%d ", buildId);for (auto & d : dependentIds)payload += fmt("%d ", d);// FIXME: apparently parameterized() doesn't support NOTIFY.txn.exec(fmt("notify build_finished, '%s'", payload)); - edit in src/hydra-queue-runner/hydra-queue-runner.cc at line 541
root.attr("nrNotificationsDone", nrNotificationsDone);root.attr("nrNotificationsFailed", nrNotificationsFailed);root.attr("nrNotificationsInProgress", nrNotificationsInProgress);root.attr("nrNotificationsPending", notificationSenderQueue.lock()->size());root.attr("nrNotificationTimeMs", nrNotificationTimeMs);uint64_t nrNotificationsTotal = nrNotificationsDone + nrNotificationsFailed;root.attr("nrNotificationTimeAvgMs", nrNotificationsTotal == 0 ? 0.0 : (float) nrNotificationTimeMs / nrNotificationsTotal); - edit in src/hydra-queue-runner/hydra-queue-runner.cc at line 787
/* Idem for notification sending. */auto maxConcurrentNotifications = config->getIntOption("max-concurrent-notifications", 2);for (uint64_t i = 0; i < maxConcurrentNotifications; ++i)std::thread(&State::notificationSender, this).detach(); - edit in src/hydra-queue-runner/hydra-queue-runner.cc at line 788
/* Enqueue notification items for builds that were finishedpreviously, but for which we didn't manage to sendnotifications. */{auto conn(dbPool.get());pqxx::work txn(*conn);auto res = txn.parameterized("select id from Builds where notificationPendingSince > 0").exec();for (auto const & row : res) {auto id = row["id"].as<BuildID>();enqueueNotificationItem({NotificationItem::Type::BuildFinished, id});}} - edit in src/hydra-queue-runner/queue-monitor.cc at line 196
notifyBuildFinished(txn, build->id, {}); - edit in src/hydra-queue-runner/queue-monitor.cc at line 201
enqueueNotificationItem({NotificationItem::Type::BuildFinished, build->id}); - edit in src/hydra-queue-runner/queue-monitor.cc at line 232
notifyBuildFinished(txn, build->id, {}); - edit in src/hydra-queue-runner/queue-monitor.cc at line 238
enqueueNotificationItem({NotificationItem::Type::BuildFinished, build->id}); - edit in src/hydra-queue-runner/state.hh at line 350[11.910]→[11.939:1092](∅→∅),[11.910]→[11.6084:6085](∅→∅),[11.1092]→[11.6084:6085](∅→∅),[11.6084]→[11.6084:6085](∅→∅),[11.6223]→[11.6223:6498](∅→∅),[11.6498]→[11.991:1058](∅→∅),[11.1058]→[11.2843:2919](∅→∅),[11.2919]→[11.1098:1191](∅→∅),[11.1098]→[11.1098:1191](∅→∅),[11.1191]→[11.2920:2976](∅→∅),[11.2976]→[11.1191:1198](∅→∅),[11.1191]→[11.1191:1198](∅→∅),[11.1198]→[11.2097:2220](∅→∅),[11.6569]→[11.2097:2220](∅→∅),[11.2220]→[11.6691:6692](∅→∅),[11.6691]→[11.6691:6692](∅→∅),[11.6692]→[11.2977:3249](∅→∅)
counter nrNotificationsDone{0};counter nrNotificationsFailed{0};counter nrNotificationsInProgress{0};counter nrNotificationTimeMs{0};/* Notification sender work queue. FIXME: if hydra-queue-runner iskilled before it has finished sending notifications about abuild, then the notifications may be lost. It would be betterto mark builds with pending notification in the database. */struct NotificationItem{enum class Type : char {BuildStarted,BuildFinished,StepFinished,};Type type;BuildID id;std::vector<BuildID> dependentIds;unsigned int stepNr;nix::Path logPath;};nix::Sync<std::queue<NotificationItem>> notificationSenderQueue;std::condition_variable notificationSenderWakeup;void enqueueNotificationItem(const NotificationItem && item){{auto notificationSenderQueue_(notificationSenderQueue.lock());notificationSenderQueue_->emplace(item);}notificationSenderWakeup.notify_one();} - replacement in src/hydra-queue-runner/state.hh at line 510
/* Thread that asynchronously invokes hydra-notify to send buildnotifications. */void notificationSender();void notifyBuildStarted(pqxx::work & txn, BuildID buildId);void notifyBuildFinished(pqxx::work & txn, BuildID buildId,const std::vector<BuildID> & dependentIds); - edit in src/script/hydra-notify at line 8
use IO::Select; - edit in src/script/hydra-notify at line 18
my $dbh = $db->storage->dbh; - replacement in src/script/hydra-notify at line 21
my $cmd = shift @ARGV or die "Syntax: hydra-notify build-started BUILD | build-finished BUILD-ID [BUILD-IDs...] | step-finished BUILD-ID STEP-NR LOG-PATH\n";$dbh->do("listen build_started");$dbh->do("listen build_finished");$dbh->do("listen step_finished");sub buildStarted {my ($buildId) = @_; - replacement in src/script/hydra-notify at line 28
my $buildId = shift @ARGV or die;my $build = $db->resultset('Builds')->find($buildId)or die "build $buildId does not exist\n";my $build = $db->resultset('Builds')->find($buildId)or die "build $buildId does not exist\n";foreach my $plugin (@plugins) {eval { $plugin->buildStarted($build); };if ($@) {print STDERR "$plugin->buildStarted: $@\n";}}} - replacement in src/script/hydra-notify at line 39
if ($cmd eq "build-finished") {sub buildFinished {my ($build, @deps) = @_; - edit in src/script/hydra-notify at line 47
- replacement in src/script/hydra-notify at line 49
foreach my $id (@ARGV) {foreach my $id (@deps) { - edit in src/script/hydra-notify at line 61
$build->update({ notificationpendingsince => undef }); - replacement in src/script/hydra-notify at line 65
elsif ($cmd eq "build-queued") {foreach my $plugin (@plugins) {eval { $plugin->buildQueued($build); };if ($@) {print STDERR "$plugin->buildQueued: $@\n";}}}sub stepFinished {my ($buildId, $stepNr, $logPath) = @_; - replacement in src/script/hydra-notify at line 68
elsif ($cmd eq "build-started") {foreach my $plugin (@plugins) {eval { $plugin->buildStarted($build); };if ($@) {print STDERR "$plugin->buildStarted: $@\n";}}}my $build = $db->resultset('Builds')->find($buildId)or die "build $buildId does not exist\n"; - edit in src/script/hydra-notify at line 71
elsif ($cmd eq "step-finished") {die if scalar @ARGV < 2;my $stepNr = shift @ARGV; - replacement in src/script/hydra-notify at line 73
my $logPath = shift @ARGV;$logPath = undef if $logPath eq "";$logPath = undef if $logPath eq "-"; - replacement in src/script/hydra-notify at line 84
else {die "unknown action ‘$cmd’";# Process builds that finished while hydra-notify wasn't running.for my $build ($db->resultset('Builds')->search({ notificationpendingsince => { '!=', undef } })){my $buildId = $build->id;print STDERR "sending notifications for build ${\$buildId}...\n";buildFinished($build);}# Process incoming notifications.my $fd = $dbh->func("getfd");my $sel = IO::Select->new($fd);while (1) {$sel->can_read;my $notify = $dbh->func("pg_notifies");next if !$notify;my ($channelName, $pid, $payload) = @$notify;#print STDERR "got '$channelName' from $pid: $payload\n";my @payload = split / /, $payload;eval {if ($channelName eq "build_started") {buildStarted(int($payload[0]));} elsif ($channelName eq "build_finished") {my $buildId = int($payload[0]);my $build = $db->resultset('Builds')->find($buildId)or die "build $buildId does not exist\n";buildFinished($build, @payload[1..$#payload]);} elsif ($channelName eq "step_finished") {stepFinished(int($payload[0]), int($payload[1]));}};if ($@) {print STDERR "error processing message '$payload' on channel '$channelName': $@\n";}