forked from lix-project/hydra
getQueuedBuilds(): Handle dependent builds first
If a build A depends on a derivation that is the top-level derivation of some build B, then we should process B before A (meaning we shouldn't make the derivation runnable before B has been added). Otherwise, the derivation will be "accounted" to A rather than B (so the build step will show up in the wrong build).
This commit is contained in:
parent
c6d504edbb
commit
b1a75c7f63
|
@ -479,7 +479,7 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store,
|
||||||
|
|
||||||
/* Grab the queued builds from the database, but don't process
|
/* Grab the queued builds from the database, but don't process
|
||||||
them yet (since we don't want a long-running transaction). */
|
them yet (since we don't want a long-running transaction). */
|
||||||
std::list<Build::ptr> newBuilds; // FIXME: use queue
|
std::multimap<Path, Build::ptr> newBuilds;
|
||||||
|
|
||||||
{
|
{
|
||||||
pqxx::work txn(conn);
|
pqxx::work txn(conn);
|
||||||
|
@ -498,20 +498,18 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store,
|
||||||
build->fullJobName = row["project"].as<string>() + ":" + row["jobset"].as<string>() + ":" + row["job"].as<string>();
|
build->fullJobName = row["project"].as<string>() + ":" + row["jobset"].as<string>() + ":" + row["job"].as<string>();
|
||||||
build->maxSilentTime = row["maxsilent"].as<int>();
|
build->maxSilentTime = row["maxsilent"].as<int>();
|
||||||
build->buildTimeout = row["timeout"].as<int>();
|
build->buildTimeout = row["timeout"].as<int>();
|
||||||
std::cerr << build->id << " " << build->buildTimeout << std::endl;
|
|
||||||
|
|
||||||
newBuilds.push_back(build);
|
newBuilds.emplace(std::make_pair(build->drvPath, build));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now instantiate build steps for each new build. The builder
|
std::set<Step::ptr> newRunnable;
|
||||||
threads can start building the runnable build steps right away,
|
unsigned int nrAdded;
|
||||||
even while we're still processing other new builds. */
|
std::function<void(Build::ptr)> createBuild;
|
||||||
for (auto & build : newBuilds) {
|
|
||||||
// FIXME: remove build from newBuilds to ensure quick destruction
|
|
||||||
// FIXME: exception handling
|
|
||||||
|
|
||||||
|
createBuild = [&](Build::ptr build) {
|
||||||
printMsg(lvlTalkative, format("loading build %1% (%2%)") % build->id % build->fullJobName);
|
printMsg(lvlTalkative, format("loading build %1% (%2%)") % build->id % build->fullJobName);
|
||||||
|
nrAdded++;
|
||||||
|
|
||||||
if (!store->isValidPath(build->drvPath)) {
|
if (!store->isValidPath(build->drvPath)) {
|
||||||
/* Derivation has been GC'ed prematurely. */
|
/* Derivation has been GC'ed prematurely. */
|
||||||
|
@ -524,12 +522,26 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store,
|
||||||
(time(0))
|
(time(0))
|
||||||
("derivation was garbage-collected prior to build").exec();
|
("derivation was garbage-collected prior to build").exec();
|
||||||
txn.commit();
|
txn.commit();
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<Step::ptr> newSteps, newRunnable;
|
std::set<Step::ptr> newSteps;
|
||||||
Step::ptr step = createStep(store, build->drvPath, newSteps, newRunnable);
|
Step::ptr step = createStep(store, build->drvPath, newSteps, newRunnable);
|
||||||
|
|
||||||
|
/* Some of the new steps may be the top level of builds that
|
||||||
|
we haven't processed yet. So do them now. This ensures that
|
||||||
|
if build A depends on build B with top-level step X, then X
|
||||||
|
will be "accounted" to B in doBuildStep(). */
|
||||||
|
for (auto & r : newSteps) {
|
||||||
|
while (true) {
|
||||||
|
auto i = newBuilds.find(r->drvPath);
|
||||||
|
if (i == newBuilds.end()) break;
|
||||||
|
Build::ptr b = i->second;
|
||||||
|
newBuilds.erase(i);
|
||||||
|
createBuild(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* If we didn't get a step, it means the step's outputs are
|
/* If we didn't get a step, it means the step's outputs are
|
||||||
all valid. So we mark this as a finished, cached build. */
|
all valid. So we mark this as a finished, cached build. */
|
||||||
if (!step) {
|
if (!step) {
|
||||||
|
@ -543,7 +555,7 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store,
|
||||||
markSucceededBuild(txn, build, res, true, now, now);
|
markSucceededBuild(txn, build, res, true, now, now);
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If any step has an unsupported system type or has a
|
/* If any step has an unsupported system type or has a
|
||||||
|
@ -591,7 +603,7 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (badStep) continue;
|
if (badStep) return;
|
||||||
|
|
||||||
/* Note: if we exit this scope prior to this, the build and
|
/* Note: if we exit this scope prior to this, the build and
|
||||||
all newly created steps are destroyed. */
|
all newly created steps are destroyed. */
|
||||||
|
@ -604,16 +616,30 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store,
|
||||||
build->toplevel = step;
|
build->toplevel = step;
|
||||||
}
|
}
|
||||||
|
|
||||||
printMsg(lvlChatty, format("added build %1% (top-level step %2%, %3% new steps, %4% new runnable steps)")
|
printMsg(lvlChatty, format("added build %1% (top-level step %2%, %3% new steps)")
|
||||||
% build->id % step->drvPath % newSteps.size() % newRunnable.size());
|
% build->id % step->drvPath % newSteps.size());
|
||||||
|
|
||||||
/* Prior to this, the build is not visible to
|
/* Prior to this, the build is not visible to
|
||||||
getDependentBuilds(). Now it is, so the build can be
|
getDependentBuilds(). Now it is, so the build can be
|
||||||
failed if a dependency fails. (It can't succeed right away
|
failed if a dependency fails. (It can't succeed right away
|
||||||
because its top-level is not runnable yet). */
|
because its top-level is not runnable yet). */
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Now instantiate build steps for each new build. The builder
|
||||||
|
threads can start building the runnable build steps right away,
|
||||||
|
even while we're still processing other new builds. */
|
||||||
|
while (!newBuilds.empty()) {
|
||||||
|
auto build = newBuilds.begin()->second;
|
||||||
|
newBuilds.erase(newBuilds.begin());
|
||||||
|
|
||||||
|
newRunnable.clear();
|
||||||
|
nrAdded = 0;
|
||||||
|
createBuild(build);
|
||||||
|
|
||||||
/* Add the new runnable build steps to ‘runnable’ and wake up
|
/* Add the new runnable build steps to ‘runnable’ and wake up
|
||||||
the builder threads. */
|
the builder threads. */
|
||||||
|
printMsg(lvlChatty, format("got %1% new runnable steps from %2% new builds") % newRunnable.size() % nrAdded);
|
||||||
for (auto & r : newRunnable)
|
for (auto & r : newRunnable)
|
||||||
makeRunnable(r);
|
makeRunnable(r);
|
||||||
}
|
}
|
||||||
|
@ -1235,8 +1261,6 @@ void State::run()
|
||||||
|
|
||||||
auto queueMonitorThread = std::thread(&State::queueMonitor, this);
|
auto queueMonitorThread = std::thread(&State::queueMonitor, this);
|
||||||
|
|
||||||
sleep(5);
|
|
||||||
|
|
||||||
std::thread(&State::dispatcher, this).detach();
|
std::thread(&State::dispatcher, this).detach();
|
||||||
|
|
||||||
queueMonitorThread.join();
|
queueMonitorThread.join();
|
||||||
|
|
Loading…
Reference in a new issue