Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jobset -> JobsetEvals by JobsetEvals.jobset_id #850

Merged
merged 10 commits into from Jan 28, 2021
125 changes: 86 additions & 39 deletions src/hydra-evaluator/hydra-evaluator.cc
Expand Up @@ -15,6 +15,56 @@ using namespace nix;

typedef std::pair<std::string, std::string> JobsetName;

class JobsetId {
public:

std::string project;
std::string jobset;
int id;


JobsetId(const std::string & project, const std::string & jobset, int id)
: project{ project }, jobset{ jobset }, id{ id }
{
}

friend bool operator== (const JobsetId & lhs, const JobsetId & rhs);
friend bool operator!= (const JobsetId & lhs, const JobsetId & rhs);
friend bool operator< (const JobsetId & lhs, const JobsetId & rhs);


friend bool operator== (const JobsetId & lhs, const JobsetName & rhs);
friend bool operator!= (const JobsetId & lhs, const JobsetName & rhs);

std::string display() const {
return str(format("%1%:%2% (jobset#%3%)") % project % jobset % id);
}
};
bool operator==(const JobsetId & lhs, const JobsetId & rhs)
{
return lhs.id == rhs.id;
}

bool operator!=(const JobsetId & lhs, const JobsetId & rhs)
{
return lhs.id != rhs.id;
}

bool operator<(const JobsetId & lhs, const JobsetId & rhs)
{
return lhs.id < rhs.id;
}

bool operator==(const JobsetId & lhs, const JobsetName & rhs)
{
return lhs.project == rhs.first && lhs.jobset == rhs.second;
}

bool operator!=(const JobsetId & lhs, const JobsetName & rhs)
{
return ! (lhs == rhs);
}

enum class EvaluationStyle
{
SCHEDULE = 1,
Expand All @@ -30,14 +80,14 @@ struct Evaluator

struct Jobset
{
JobsetName name;
JobsetId name;
std::optional<EvaluationStyle> evaluation_style;
time_t lastCheckedTime, triggerTime;
int checkInterval;
Pid pid;
};

typedef std::map<JobsetName, Jobset> Jobsets;
typedef std::map<JobsetId, Jobset> Jobsets;

std::optional<JobsetName> evalOne;

Expand Down Expand Up @@ -68,16 +118,18 @@ struct Evaluator
pqxx::work txn(*conn);

auto res = txn.exec
("select project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled from Jobsets j join Projects p on j.project = p.name "
("select j.id as id, project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled "
"from Jobsets j "
"join Projects p on j.project = p.name "
"where j.enabled != 0 and p.enabled != 0");


auto state(state_.lock());

std::set<JobsetName> seen;
std::set<JobsetId> seen;

for (auto const & row : res) {
auto name = JobsetName{row["project"].as<std::string>(), row["name"].as<std::string>()};
auto name = JobsetId{row["project"].as<std::string>(), row["name"].as<std::string>(), row["id"].as<int>()};

if (evalOne && name != *evalOne) continue;

Expand Down Expand Up @@ -111,7 +163,7 @@ struct Evaluator
if (seen.count(i->first))
++i;
else {
printInfo("forgetting jobset ‘%s:%s’", i->first.first, i->first.second);
printInfo("forgetting jobset ‘%s’", i->first.display());
i = state->jobsets.erase(i);
}
}
Expand All @@ -120,25 +172,24 @@ struct Evaluator
{
time_t now = time(0);

printInfo("starting evaluation of jobset ‘%s:%s’ (last checked %d s ago)",
jobset.name.first, jobset.name.second,
printInfo("starting evaluation of jobset ‘%s’ (last checked %d s ago)",
jobset.name.display(),
now - jobset.lastCheckedTime);

{
auto conn(dbPool.get());
pqxx::work txn(*conn);
txn.exec_params0
("update Jobsets set startTime = $1 where project = $2 and name = $3",
("update Jobsets set startTime = $1 where id = $2",
now,
jobset.name.first,
jobset.name.second);
jobset.name.id);
txn.commit();
}

assert(jobset.pid == -1);

jobset.pid = startProcess([&]() {
Strings args = { "hydra-eval-jobset", jobset.name.first, jobset.name.second };
Strings args = { "hydra-eval-jobset", jobset.name.project, jobset.name.jobset };
execvp(args.front().c_str(), stringsToCharPtrs(args).data());
throw SysError("executing ‘%1%’", args.front());
});
Expand All @@ -152,23 +203,23 @@ struct Evaluator
{
if (jobset.pid != -1) {
// Already running.
debug("shouldEvaluate %s:%s? no: already running",
jobset.name.first, jobset.name.second);
debug("shouldEvaluate %s? no: already running",
jobset.name.display());
return false;
}

if (jobset.triggerTime != std::numeric_limits<time_t>::max()) {
// An evaluation of this Jobset is requested
debug("shouldEvaluate %s:%s? yes: requested",
jobset.name.first, jobset.name.second);
debug("shouldEvaluate %s? yes: requested",
jobset.name.display());
return true;
}

if (jobset.checkInterval <= 0) {
// Automatic scheduling is disabled. We allow requested
// evaluations, but never schedule start one.
debug("shouldEvaluate %s:%s? no: checkInterval <= 0",
jobset.name.first, jobset.name.second);
debug("shouldEvaluate %s? no: checkInterval <= 0",
jobset.name.display());
return false;
}

Expand All @@ -184,16 +235,15 @@ struct Evaluator
if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) {
auto evaluation_res = txn.parameterized
("select id from JobsetEvals "
"where project = $1 and jobset = $2 "
"where jobset_id = $1 "
"order by id desc limit 1")
(jobset.name.first)
(jobset.name.second)
(jobset.name.id)
.exec();

if (evaluation_res.empty()) {
// First evaluation, so allow scheduling.
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no prior eval",
jobset.name.first, jobset.name.second);
debug("shouldEvaluate(one-at-a-time) %s? yes: no prior eval",
jobset.name.display());
return true;
}

Expand All @@ -212,20 +262,20 @@ struct Evaluator
// If the previous evaluation has no unfinished builds
// schedule!
if (unfinished_build_res.empty()) {
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no unfinished builds",
jobset.name.first, jobset.name.second);
debug("shouldEvaluate(one-at-a-time) %s? yes: no unfinished builds",
jobset.name.display());
return true;
} else {
debug("shouldEvaluate(one-at-a-time) %s:%s? no: at least one unfinished build",
jobset.name.first, jobset.name.second);
jobset.name.display());
return false;
}


} else {
// EvaluationStyle::ONESHOT, EvaluationStyle::SCHEDULED
debug("shouldEvaluate(oneshot/scheduled) %s:%s? yes: checkInterval elapsed",
jobset.name.first, jobset.name.second);
debug("shouldEvaluate(oneshot/scheduled) %s? yes: checkInterval elapsed",
jobset.name.display());
return true;
}
}
Expand Down Expand Up @@ -350,8 +400,8 @@ struct Evaluator
auto & jobset(i.second);

if (jobset.pid == pid) {
printInfo("evaluation of jobset ‘%s:%s’ %s",
jobset.name.first, jobset.name.second, statusToString(status));
printInfo("evaluation of jobset ‘%s’ %s",
jobset.name.display(), statusToString(status));

auto now = time(0);

Expand All @@ -367,23 +417,20 @@ struct Evaluator
jobset from getting stuck in an endless
failing eval loop. */
txn.exec_params0
("update Jobsets set triggerTime = null where project = $1 and name = $2 and startTime is not null and triggerTime <= startTime",
jobset.name.first,
jobset.name.second);
("update Jobsets set triggerTime = null where id = $1 and startTime is not null and triggerTime <= startTime",
jobset.name.id);

/* Clear the start time. */
txn.exec_params0
("update Jobsets set startTime = null where project = $1 and name = $2",
jobset.name.first,
jobset.name.second);
("update Jobsets set startTime = null where id = $1",
jobset.name.id);

if (!WIFEXITED(status) || WEXITSTATUS(status) > 1) {
txn.exec_params0
("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where project = $3 and name = $4",
("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where id = $3",
fmt("evaluation %s", statusToString(status)),
now,
jobset.name.first,
jobset.name.second);
jobset.name.id);
}

txn.commit();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Hydra/Controller/Admin.pm
Expand Up @@ -34,7 +34,7 @@ sub machines : Chained('admin') PathPart('machines') Args(0) {
sub clear_queue_non_current : Chained('admin') PathPart('clear-queue-non-current') Args(0) {
my ($self, $c) = @_;
my $builds = $c->model('DB::Builds')->search(
{ id => { -in => \ "select id from Builds where id in ((select id from Builds where finished = 0) except (select build from JobsetEvalMembers where eval in (select max(id) from JobsetEvals where hasNewBuilds = 1 group by project, jobset)))" }
{ id => { -in => \ "select id from Builds where id in ((select id from Builds where finished = 0) except (select build from JobsetEvalMembers where eval in (select max(id) from JobsetEvals where hasNewBuilds = 1 group by jobset_id)))" }
});
my $n = cancelBuilds($c->model('DB')->schema, $builds);
$c->flash->{successMsg} = "$n builds have been cancelled.";
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Hydra/Controller/JobsetEval.pm
Expand Up @@ -16,8 +16,8 @@ sub evalChain : Chained('/') PathPart('eval') CaptureArgs(1) {
or notFound($c, "Evaluation $evalId doesn't exist.");

$c->stash->{eval} = $eval;
$c->stash->{project} = $eval->project;
$c->stash->{jobset} = $eval->jobset;
$c->stash->{project} = $eval->jobset->project;
}


Expand Down
2 changes: 1 addition & 1 deletion src/lib/Hydra/Helper/Nix.pm
Expand Up @@ -219,7 +219,7 @@ sub getEvals {
foreach my $curEval (@evals) {

my ($prevEval) = $c->model('DB::JobsetEvals')->search(
{ project => $curEval->get_column('project'), jobset => $curEval->get_column('jobset')
{ jobset_id => $curEval->get_column('jobset_id')
, hasnewbuilds => 1, id => { '<', $curEval->id } },
{ order_by => "id DESC", rows => 1 });

Expand Down