Skip to content

Commit 34f2477

Browse files
emilazycentromere
andcommitted
libstore: add load-limit setting to control parallelism
Closes: NixOS#7091 Closes: NixOS#6855 Closes: NixOS#8105 Co-authored-by: Alex Wied <[email protected]>
1 parent c4213f0 commit 34f2477

File tree

10 files changed

+93
-1
lines changed

10 files changed

+93
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Release X.Y (202?-??-??)
2+
3+
- Add a `load-limit` setting to control builder parallelism. This has
4+
also been backported to the 2.18 and later release branches.

src/libstore/daemon.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ struct ClientSettings
199199
time_t maxSilentTime;
200200
bool verboseBuild;
201201
unsigned int buildCores;
202+
std::optional<unsigned int> loadLimit;
202203
bool useSubstitutes;
203204
StringMap overrides;
204205

@@ -212,6 +213,7 @@ struct ClientSettings
212213
settings.maxSilentTime = maxSilentTime;
213214
settings.verboseBuild = verboseBuild;
214215
settings.buildCores = buildCores;
216+
settings.loadLimit.assign(loadLimit);
215217
settings.useSubstitutes = useSubstitutes;
216218

217219
for (auto & i : overrides) {

src/libstore/globals.hh

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ public:
166166
R"(
167167
Sets the value of the `NIX_BUILD_CORES` environment variable in the [invocation of the `builder` executable](@docroot@/language/derivations.md#builder-execution) of a derivation.
168168
The `builder` executable can use this variable to control its own maximum amount of parallelism.
169-
170169
<!--
171170
FIXME(@fricklerhandwerk): I don't think this should even be mentioned here.
172171
A very generic example using `derivation` and `xargs` may be more appropriate to explain the mechanism.
@@ -176,6 +175,8 @@ public:
176175
177176
The value `0` means that the `builder` should use all available CPU cores in the system.
178177
178+
The [`load-limit`](#conf-load-limit) setting can be used to limit the total amount of build parallelism based on system load average.
179+
179180
> **Note**
180181
>
181182
> The number of parallel local Nix build jobs is independently controlled with the [`max-jobs`](#conf-max-jobs) setting.
@@ -184,6 +185,33 @@ public:
184185
// Don't document the machine-specific default value
185186
false};
186187

188+
Setting<std::optional<unsigned int>> loadLimit{
189+
this,
190+
{ getDefaultCores() },
191+
"load-limit",
192+
R"(
193+
Sets the value of the `NIX_LOAD_LIMIT` environment variable in the
194+
invocation of builders. Builders can use this value at their discretion
195+
to dynamically control the amount of parallelism with respect to the
196+
machine's load average.
197+
198+
For instance, a builder could use the value to set the `-l` flag to GNU
199+
Make. In this case, if the load average of the machine exceeds
200+
`NIX_LOAD_LIMIT`, the amount of parallelism will be dynamically
201+
reduced.
202+
203+
By default, it is set to the number of cores on the machine.
204+
205+
On busy machines where Nix co-exists with other workloads, or where
206+
build throughput is paramount and memory usage is not a bottleneck, the
207+
default value may not work as intended. In this case, `load-limit`
208+
should be set to a higher value, or to `none` to prevent the
209+
`NIX_LOAD_LIMIT` variable being set at all.
210+
)",
211+
{},
212+
// Don't document the machine-specific default value
213+
false};
214+
187215
/**
188216
* Read-only mode. Don't copy stuff to the store, don't change
189217
* the database.

src/libstore/unix/build/local-derivation-goal.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,10 @@ void LocalDerivationGoal::initEnv()
11561156
/* The maximum number of cores to utilize for parallel building. */
11571157
env["NIX_BUILD_CORES"] = fmt("%d", settings.buildCores);
11581158

1159+
/* Provide a load average limit for build tools to throttle jobs. */
1160+
if (settings.loadLimit.get())
1161+
env["NIX_LOAD_LIMIT"] = fmt("%d", settings.loadLimit.get().value());
1162+
11591163
initTmpDir();
11601164

11611165
/* Compatibility hack with Nix <= 0.7: if this is a fixed-output

src/libutil/config-impl.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
105105

106106
DECLARE_CONFIG_SERIALISER(std::string)
107107
DECLARE_CONFIG_SERIALISER(std::optional<std::string>)
108+
DECLARE_CONFIG_SERIALISER(std::optional<unsigned int>)
108109
DECLARE_CONFIG_SERIALISER(bool)
109110
DECLARE_CONFIG_SERIALISER(Strings)
110111
DECLARE_CONFIG_SERIALISER(StringSet)

src/libutil/config.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,25 @@ template<> std::string BaseSetting<std::optional<std::string>>::to_string() cons
273273
return value ? *value : "";
274274
}
275275

276+
template<> std::optional<unsigned int> BaseSetting<std::optional<unsigned int>>::parse(const std::string & str) const
277+
{
278+
if (str == "none") return std::nullopt;
279+
else {
280+
if (auto n = string2Int<unsigned int>(str))
281+
return { *n };
282+
else
283+
throw UsageError("configuration setting '%s' should be 'none' or an integer", name);
284+
}
285+
}
286+
287+
template<> std::string BaseSetting<std::optional<unsigned int>>::to_string() const
288+
{
289+
if (value)
290+
return std::to_string(value.value());
291+
else
292+
return "none";
293+
}
294+
276295
template<> bool BaseSetting<bool>::parse(const std::string & str) const
277296
{
278297
if (str == "true" || str == "yes" || str == "1")

src/nix-build/nix-build.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,8 @@ static void main_nix_build(int argc, char * * argv)
541541
env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmp;
542542
env["NIX_STORE"] = store->storeDir;
543543
env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores);
544+
if (settings.loadLimit.get())
545+
env["NIX_LOAD_LIMIT"] = std::to_string(settings.loadLimit.get().value());
544546

545547
auto passAsFile = tokenizeString<StringSet>(getOr(drv.env, "passAsFile", ""));
546548

tests/functional/load-limit.nix

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
with import ./config.nix;
2+
3+
mkDerivation {
4+
name = "load-limit";
5+
buildCommand = ''
6+
printf '%s' "''${NIX_LOAD_LIMIT-unset}" > "$out"
7+
'';
8+
}

tests/functional/load-limit.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
3+
source common.sh
4+
5+
TODO_NixOS
6+
7+
clearStore
8+
9+
outPath=$(nix-build --load-limit 100 load-limit.nix --no-out-link)
10+
text=$(cat "$outPath")
11+
if test "$text" != "100"; then exit 1; fi
12+
13+
clearStore
14+
15+
outPath=$(nix-build --load-limit 0 load-limit.nix --no-out-link)
16+
text=$(cat "$outPath")
17+
if test "$text" != "0"; then exit 1; fi
18+
19+
clearStore
20+
21+
outPath=$(nix-build --load-limit none load-limit.nix --no-out-link)
22+
text=$(cat "$outPath")
23+
if test "$text" != "unset"; then exit 1; fi

tests/functional/local.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ nix_tests = \
114114
ssh-relay.sh \
115115
build.sh \
116116
build-delete.sh \
117+
load-limit.sh \
117118
output-normalization.sh \
118119
selfref-gc.sh \
119120
db-migration.sh \

0 commit comments

Comments
 (0)