From e922611ce3936f9c8bb1978423fddef730ba045b Mon Sep 17 00:00:00 2001 From: Clemens Wasser Date: Thu, 17 Nov 2022 21:27:42 +0100 Subject: [PATCH] Allow setting the priority of spawned subprocess --- src/build.cc | 3 ++- src/build.h | 6 ++++-- src/ninja.cc | 35 ++++++++++++++++++++++++----------- src/subprocess-posix.cc | 16 +++++++++++++--- src/subprocess-win32.cc | 23 ++++++++++++++++++++--- src/subprocess.h | 9 +++++++-- 6 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/build.cc b/src/build.cc index fb29732a54..fbd6327986 100644 --- a/src/build.cc +++ b/src/build.cc @@ -606,7 +606,8 @@ size_t RealCommandRunner::CanRunMore() const { bool RealCommandRunner::StartCommand(Edge* edge) { string command = edge->EvaluateCommand(); - Subprocess* subproc = subprocs_.Add(command, edge->use_console()); + Subprocess* subproc = + subprocs_.Add(command, config_.subprocess_priority, edge->use_console()); if (!subproc) return false; subproc_to_edge_.insert(make_pair(subproc, edge)); diff --git a/src/build.h b/src/build.h index 471f0b2caa..a8491f84fe 100644 --- a/src/build.h +++ b/src/build.h @@ -15,6 +15,7 @@ #ifndef NINJA_BUILD_H_ #define NINJA_BUILD_H_ +#include #include #include #include @@ -165,8 +166,8 @@ struct CommandRunner { /// Options (e.g. verbosity, parallelism) passed to a build. struct BuildConfig { - BuildConfig() : verbosity(NORMAL), dry_run(false), parallelism(1), - failures_allowed(1), max_load_average(-0.0f) {} + BuildConfig() : verbosity(NORMAL), dry_run(false), parallelism(1), failures_allowed(1), + subprocess_priority(INT_MIN), max_load_average(-0.0f) {} enum Verbosity { QUIET, // No output -- used when testing. @@ -178,6 +179,7 @@ struct BuildConfig { bool dry_run; int parallelism; int failures_allowed; + int subprocess_priority; /// The maximum load average we must not exceed. A negative value /// means that we do not have any limit. double max_load_average; diff --git a/src/ninja.cc b/src/ninja.cc index 2902359f15..1f1493a3a7 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -224,18 +224,22 @@ void Usage(const BuildConfig& config) { " -v, --verbose show all command lines while building\n" " --quiet don't show progress status, just command output\n" "\n" -" -C DIR change to DIR before doing anything else\n" -" -f FILE specify input build file [default=build.ninja]\n" +" -C DIR change to DIR before doing anything else\n" +" -f FILE specify input build file [default=build.ninja]\n" "\n" -" -j N run N jobs in parallel (0 means infinity) [default=%d on this system]\n" -" -k N keep going until N jobs fail (0 means infinity) [default=1]\n" -" -l N do not start new jobs if the load average is greater than N\n" -" -n dry run (don't run commands but act like they succeeded)\n" +" -j N run N jobs in parallel (0 means infinity) [default=%d on this system]\n" +" -k N keep going until N jobs fail (0 means infinity) [default=1]\n" +" -l N do not start new jobs if the load average is greater than N\n" +" -n dry run (don't run commands but act like they succeeded)\n" +" -p PRIORITY set the priority/niceness of spawned processes\n" +" the range of possible values is platform dependent:\n" +" - POSIX: depdending on your scheduler\n" +" - Windows: between -20 (highest priority) and 20 (lowest priority)\n" "\n" -" -d MODE enable debugging (use '-d list' to list modes)\n" -" -t TOOL run a subtool (use '-t list' to list subtools)\n" -" terminates toplevel options; further flags are passed to the tool\n" -" -w FLAG adjust warnings (use '-w list' to list warnings)\n", +" -d MODE enable debugging (use '-d list' to list modes)\n" +" -t TOOL run a subtool (use '-t list' to list subtools)\n" +" terminates toplevel options; further flags are passed to the tool\n" +" -w FLAG adjust warnings (use '-w list' to list warnings)\n", kNinjaVersion, config.parallelism); } @@ -1447,7 +1451,7 @@ int ReadFlags(int* argc, char*** argv, int opt; while (!options->tool && - (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions, + (opt = getopt_long(*argc, *argv, "d:f:j:k:l:p:nt:vw:C:h", kLongOptions, NULL)) != -1) { switch (opt) { case 'd': @@ -1492,6 +1496,15 @@ int ReadFlags(int* argc, char*** argv, case 'n': config->dry_run = true; break; + case 'p': { + char* end; + int value = strtol(optarg, &end, 10); + if (*end != 0) + Fatal("-p parameter not numeric: did you mean -p 0?"); + + config->subprocess_priority = value; + break; + } case 't': options->tool = ChooseTool(optarg); if (!options->tool) diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index 8e785406c9..8da29ca3e3 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #if defined(USE_PPOLL) #include @@ -48,7 +51,8 @@ Subprocess::~Subprocess() { Finish(); } -bool Subprocess::Start(SubprocessSet* set, const string& command) { +bool Subprocess::Start(SubprocessSet* set, const string& command, + int priority) { int output_pipe[2]; if (pipe(output_pipe) < 0) Fatal("pipe: %s", strerror(errno)); @@ -123,6 +127,12 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { if (err != 0) Fatal("posix_spawn: %s", strerror(err)); + if (priority != INT_MIN) { + err = setpriority(PRIO_PROCESS, pid_, priority); + if (err != 0 && err != ESRCH) + Fatal("setpriority: %s", strerror(errno)); + } + err = posix_spawnattr_destroy(&attr); if (err != 0) Fatal("posix_spawnattr_destroy: %s", strerror(err)); @@ -238,9 +248,9 @@ SubprocessSet::~SubprocessSet() { Fatal("sigprocmask: %s", strerror(errno)); } -Subprocess *SubprocessSet::Add(const string& command, bool use_console) { +Subprocess *SubprocessSet::Add(const string& command, int priority, bool use_console) { Subprocess *subprocess = new Subprocess(use_console); - if (!subprocess->Start(this, command)) { + if (!subprocess->Start(this, command, priority)) { delete subprocess; return 0; } diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc index ff3baaca7f..ff993f003d 100644 --- a/src/subprocess-win32.cc +++ b/src/subprocess-win32.cc @@ -74,7 +74,22 @@ HANDLE Subprocess::SetupPipe(HANDLE ioport) { return output_write_child; } -bool Subprocess::Start(SubprocessSet* set, const string& command) { +static DWORD POSIXPriorityToWin32(int unixPriority) { + if (unixPriority <= -20) { + return HIGH_PRIORITY_CLASS; // ..-20 + } else if (unixPriority < 0) { + return ABOVE_NORMAL_PRIORITY_CLASS; // -19..-1 + } else if (unixPriority == 0) { + return NORMAL_PRIORITY_CLASS; // 0 + } else if (unixPriority < 20) { + return BELOW_NORMAL_PRIORITY_CLASS; // 1..19 + } else { + return IDLE_PRIORITY_CLASS; // 20.. + } +} + +bool Subprocess::Start(SubprocessSet* set, const string& command, + int priority) { HANDLE child_pipe = SetupPipe(set->ioport_); SECURITY_ATTRIBUTES security_attributes; @@ -106,6 +121,8 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { // Ninja handles ctrl-c, except for subprocesses in console pools. DWORD process_flags = use_console_ ? 0 : CREATE_NEW_PROCESS_GROUP; + if (priority != INT_MIN) + process_flags |= POSIXPriorityToWin32(priority); // Do not prepend 'cmd /c' on Windows, this breaks command // lines greater than 8,191 chars. @@ -238,9 +255,9 @@ BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) { return FALSE; } -Subprocess *SubprocessSet::Add(const string& command, bool use_console) { +Subprocess* SubprocessSet::Add(const string& command, int priority, bool use_console) { Subprocess *subprocess = new Subprocess(use_console); - if (!subprocess->Start(this, command)) { + if (!subprocess->Start(this, command, priority)) { delete subprocess; return 0; } diff --git a/src/subprocess.h b/src/subprocess.h index 9e3d2ee98f..f513208112 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -52,7 +52,11 @@ struct Subprocess { private: Subprocess(bool use_console); - bool Start(struct SubprocessSet* set, const std::string& command); + // priority: + // - The range of possible values is platform dependent + // - POSIX: variable between scheduling policies between sched_get_priority_min() and sched_get_priority_max() + // - Windows: between -20 (highest priority) and 20 (lowest priority) + bool Start(struct SubprocessSet* set, const std::string& command, int priority); void OnPipeReady(); std::string buf_; @@ -83,7 +87,8 @@ struct SubprocessSet { SubprocessSet(); ~SubprocessSet(); - Subprocess* Add(const std::string& command, bool use_console = false); + Subprocess* Add(const std::string& command, int priority = 0, + bool use_console = false); bool DoWork(); Subprocess* NextFinished(); void Clear();