Skip to content

Commit

Permalink
Merge pull request NixOS#8299 from urbas/max-substitution-jobs
Browse files Browse the repository at this point in the history
`max-substitution-jobs` setting
  • Loading branch information
edolstra authored May 12, 2023
2 parents f60b215 + 1318513 commit 643b8d2
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/libstore/build/derivation-goal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ struct DerivationGoal : public Goal
void waiteeDone(GoalPtr waitee, ExitCode result) override;

StorePathSet exportReferences(const StorePathSet & storePaths);

JobCategory jobCategory() override { return JobCategory::Build; };
};

MakeError(NotDeterministic, BuildError);
Expand Down
4 changes: 3 additions & 1 deletion src/libstore/build/drv-output-substitution-goal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Worker;
class DrvOutputSubstitutionGoal : public Goal {

/**
* The drv output we're trying to substitue
* The drv output we're trying to substitute
*/
DrvOutput id;

Expand Down Expand Up @@ -72,6 +72,8 @@ public:

void work() override;
void handleEOF(int fd) override;

JobCategory jobCategory() override { return JobCategory::Substitution; };
};

}
13 changes: 13 additions & 0 deletions src/libstore/build/goal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ typedef std::set<WeakGoalPtr, std::owner_less<WeakGoalPtr>> WeakGoals;
*/
typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;

/**
* Used as a hint to the worker on how to schedule a particular goal. For example,
* builds are typically CPU- and memory-bound, while substitutions are I/O bound.
* Using this information, the worker might decide to schedule more or fewer goals
* of each category in parallel.
*/
enum struct JobCategory {
Build,
Substitution,
};

struct Goal : public std::enable_shared_from_this<Goal>
{
typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
Expand Down Expand Up @@ -150,6 +161,8 @@ public:
void amDone(ExitCode result, std::optional<Error> ex = {});

virtual void cleanup() { }

virtual JobCategory jobCategory() = 0;
};

void addToWeakGoals(WeakGoals & goals, GoalPtr p);
Expand Down
2 changes: 1 addition & 1 deletion src/libstore/build/substitution-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ void PathSubstitutionGoal::tryToRun()
if maxBuildJobs == 0 (no local builds allowed), we still allow
a substituter to run. This is because substitutions cannot be
distributed to another machine via the build hook. */
if (worker.getNrLocalBuilds() >= std::max(1U, (unsigned int) settings.maxBuildJobs)) {
if (worker.getNrSubstitutions() >= std::max(1U, (unsigned int) settings.maxSubstitutionJobs)) {
worker.waitForBuildSlot(shared_from_this());
return;
}
Expand Down
2 changes: 2 additions & 0 deletions src/libstore/build/substitution-goal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ public:
void handleEOF(int fd) override;

void cleanup() override;

JobCategory jobCategory() override { return JobCategory::Substitution; };
};

}
25 changes: 21 additions & 4 deletions src/libstore/build/worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Worker::Worker(Store & store, Store & evalStore)
{
/* Debugging: prevent recursive workers. */
nrLocalBuilds = 0;
nrSubstitutions = 0;
lastWokenUp = steady_time_point::min();
permanentFailure = false;
timedOut = false;
Expand Down Expand Up @@ -176,6 +177,12 @@ unsigned Worker::getNrLocalBuilds()
}


unsigned Worker::getNrSubstitutions()
{
return nrSubstitutions;
}


void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
bool inBuildSlot, bool respectTimeouts)
{
Expand All @@ -187,7 +194,10 @@ void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
child.inBuildSlot = inBuildSlot;
child.respectTimeouts = respectTimeouts;
children.emplace_back(child);
if (inBuildSlot) nrLocalBuilds++;
if (inBuildSlot) {
if (goal->jobCategory() == JobCategory::Substitution) nrSubstitutions++;
else nrLocalBuilds++;
}
}


Expand All @@ -198,8 +208,13 @@ void Worker::childTerminated(Goal * goal, bool wakeSleepers)
if (i == children.end()) return;

if (i->inBuildSlot) {
assert(nrLocalBuilds > 0);
nrLocalBuilds--;
if (goal->jobCategory() == JobCategory::Substitution) {
assert(nrSubstitutions > 0);
nrSubstitutions--;
} else {
assert(nrLocalBuilds > 0);
nrLocalBuilds--;
}
}

children.erase(i);
Expand All @@ -220,7 +235,9 @@ void Worker::childTerminated(Goal * goal, bool wakeSleepers)
void Worker::waitForBuildSlot(GoalPtr goal)
{
debug("wait for build slot");
if (getNrLocalBuilds() < settings.maxBuildJobs)
bool isSubstitutionGoal = goal->jobCategory() == JobCategory::Substitution;
if ((!isSubstitutionGoal && getNrLocalBuilds() < settings.maxBuildJobs) ||
(isSubstitutionGoal && getNrSubstitutions() < settings.maxSubstitutionJobs))
wakeUp(goal); /* we can do it right away */
else
addToWeakGoals(wantingToBuild, goal);
Expand Down
19 changes: 14 additions & 5 deletions src/libstore/build/worker.hh
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,16 @@ private:
std::list<Child> children;

/**
* Number of build slots occupied. This includes local builds and
* substitutions but not remote builds via the build hook.
* Number of build slots occupied. This includes local builds but does not
* include substitutions or remote builds via the build hook.
*/
unsigned int nrLocalBuilds;

/**
* Number of substitution slots occupied.
*/
unsigned int nrSubstitutions;

/**
* Maps used to prevent multiple instantiations of a goal for the
* same derivation / path.
Expand Down Expand Up @@ -220,12 +225,16 @@ public:
void wakeUp(GoalPtr goal);

/**
* Return the number of local build and substitution processes
* currently running (but not remote builds via the build
* hook).
* Return the number of local build processes currently running (but not
* remote builds via the build hook).
*/
unsigned int getNrLocalBuilds();

/**
* Return the number of substitution processes currently running.
*/
unsigned int getNrSubstitutions();

/**
* Registers a running child process. `inBuildSlot` means that
* the process counts towards the jobs limit.
Expand Down
9 changes: 9 additions & 0 deletions src/libstore/globals.hh
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ public:
)",
{"build-max-jobs"}};

Setting<unsigned int> maxSubstitutionJobs{
this, 16, "max-substitution-jobs",
R"(
This option defines the maximum number of substitution jobs that Nix
will try to run in parallel. The default is `16`. The minimum value
one can choose is `1` and lower values will be interpreted as `1`.
)",
{"substitution-max-jobs"}};

Setting<unsigned int> buildCores{
this,
getDefaultCores(),
Expand Down

0 comments on commit 643b8d2

Please sign in to comment.