Allow setting the Blaze server's QoS class via a --macos_qos_class flag.
This adds a new flag to set the QoS class of the Bazel server and accepts
selecting any of the possible classes exposed by the system. We will use
this flag to evaluate the behavior of Bazel under different classes because
it is not clear which one we should be using. (We tried forcing the class
to be utility, but that caused regressions in some cases.)
This is essentially a retry of https://github.com/bazelbuild/bazel/commit/087734009801242b83655efb863b2d5a761ae3dc but gated by a flag (which
is how it should have been done in the first place).
Note that the flag exists on all platforms to ensure that a bazelrc file
shared across different people works in all cases, but this flag is ignored
in non-macOS systems.
Addresses https://github.com/bazelbuild/bazel/issues/7446.
RELNOTES: None.
PiperOrigin-RevId: 238440116
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 0d10e0c..29d1db9 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -710,7 +710,8 @@
return ExecuteDaemon(exe, jvm_args_vector, PrepareEnvironmentForJvm(),
globals->jvm_log_file, globals->jvm_log_file_append,
- binaries_dir, server_dir, server_startup);
+ binaries_dir, server_dir, globals->options,
+ server_startup);
}
// Replace this process with blaze in standalone/batch mode.
diff --git a/src/main/cpp/blaze_util_darwin.cc b/src/main/cpp/blaze_util_darwin.cc
index aadee1b..d40caab 100644
--- a/src/main/cpp/blaze_util_darwin.cc
+++ b/src/main/cpp/blaze_util_darwin.cc
@@ -21,7 +21,9 @@
#include <sys/un.h>
#include <libproc.h>
+#include <pthread/spawn.h>
#include <signal.h>
+#include <spawn.h>
#include <stdlib.h>
#include <unistd.h>
@@ -32,6 +34,7 @@
#include <cstring>
#include "src/main/cpp/blaze_util.h"
+#include "src/main/cpp/startup_options.h"
#include "src/main/cpp/util/errors.h"
#include "src/main/cpp/util/exit_code.h"
#include "src/main/cpp/util/file.h"
@@ -193,6 +196,11 @@
return javabase.substr(0, javabase.length()-1);
}
+int ConfigureDaemonProcess(posix_spawnattr_t *attrp,
+ const StartupOptions *options) {
+ return posix_spawnattr_set_qos_class_np(attrp, options->macos_qos_class);
+}
+
void WriteSystemSpecificProcessIdentifier(
const string& server_dir, pid_t server_pid) {
}
diff --git a/src/main/cpp/blaze_util_freebsd.cc b/src/main/cpp/blaze_util_freebsd.cc
index 9cb89bd..a0d4387 100644
--- a/src/main/cpp/blaze_util_freebsd.cc
+++ b/src/main/cpp/blaze_util_freebsd.cc
@@ -16,6 +16,7 @@
#include <limits.h>
#include <pwd.h>
#include <signal.h>
+#include <spawn.h>
#include <string.h> // strerror
#include <sys/mount.h>
#include <sys/param.h>
@@ -154,6 +155,12 @@
return "/usr/local/openjdk8";
}
+int ConfigureDaemonProcess(posix_spawnattr_t *attrp,
+ const StartupOptions *options) {
+ // No interesting platform-specific details to configure on this platform.
+ return 0;
+}
+
void WriteSystemSpecificProcessIdentifier(
const string& server_dir, pid_t server_pid) {
}
diff --git a/src/main/cpp/blaze_util_linux.cc b/src/main/cpp/blaze_util_linux.cc
index e6e6d64..012a1a5 100644
--- a/src/main/cpp/blaze_util_linux.cc
+++ b/src/main/cpp/blaze_util_linux.cc
@@ -17,6 +17,7 @@
#include <linux/magic.h>
#include <pwd.h>
#include <signal.h>
+#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // strerror
@@ -203,6 +204,12 @@
return true;
}
+int ConfigureDaemonProcess(posix_spawnattr_t* attrp,
+ const StartupOptions* options) {
+ // No interesting platform-specific details to configure on this platform.
+ return 0;
+}
+
void WriteSystemSpecificProcessIdentifier(
const string& server_dir, pid_t server_pid) {
string pid_string = ToString(server_pid);
diff --git a/src/main/cpp/blaze_util_platform.h b/src/main/cpp/blaze_util_platform.h
index 56d815b..98244db 100644
--- a/src/main/cpp/blaze_util_platform.h
+++ b/src/main/cpp/blaze_util_platform.h
@@ -70,6 +70,7 @@
} // namespace embedded_binaries
struct GlobalVariables;
+class StartupOptions;
class SignalHandler {
public:
@@ -157,6 +158,7 @@
const bool daemon_output_append,
const std::string& binaries_dir,
const std::string& server_dir,
+ const StartupOptions* options,
BlazeServerStartup** server_startup);
// A character used to separate paths in a list.
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index 04e8696..6ed6a41 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -276,6 +276,11 @@
BAZEL_LOG(INFO) << "Invoking binary " << exe << " in "
<< blaze_util::GetCwd();
+ // TODO(jmmv): This execution does not respect any settings we might apply
+ // to the server process with ConfigureDaemonProcess when executed in the
+ // background as a daemon. Because we use that function to lower the
+ // priority of Bazel on macOS from a QoS perspective, this could have
+ // adverse scheduling effects on any tools invoked via ExecuteProgram.
CharPP argv(args_vector);
execv(exe.c_str(), argv.get());
}
@@ -325,6 +330,13 @@
}
}
+// Sets platform-specific attributes for the creation of the daemon process.
+//
+// Returns zero on success or -1 on error, in which case errno is set to the
+// corresponding error details.
+int ConfigureDaemonProcess(posix_spawnattr_t* attrp,
+ const StartupOptions* options);
+
void WriteSystemSpecificProcessIdentifier(
const string& server_dir, pid_t server_pid);
@@ -335,6 +347,7 @@
const bool daemon_output_append,
const string& binaries_dir,
const string& server_dir,
+ const StartupOptions* options,
BlazeServerStartup** server_startup) {
const string pid_file = blaze_util::JoinPath(server_dir, kServerPidFile);
const string daemonize = blaze_util::JoinPath(binaries_dir, "daemonize");
@@ -366,15 +379,26 @@
<< "Failed to modify posix_spawn_file_actions: "<< GetLastErrorString();
}
+ posix_spawnattr_t attrp;
+ if (posix_spawnattr_init(&attrp) == -1) {
+ BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
+ << "Failed to create posix_spawnattr: " << GetLastErrorString();
+ }
+ if (ConfigureDaemonProcess(&attrp, options) == -1) {
+ BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
+ << "Failed to modify posix_spawnattr: " << GetLastErrorString();
+ }
+
pid_t transient_pid;
- if (posix_spawn(&transient_pid, daemonize.c_str(), &file_actions, NULL,
- CharPP(daemonize_args).get(), CharPP(env).get()) == -1) {
+ if (posix_spawn(&transient_pid, daemonize.c_str(), &file_actions, &attrp,
+ CharPP(daemonize_args).get(), CharPP(env).get()) == -1) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "Failed to execute JVM via " << daemonize
<< ": " << GetLastErrorString();
}
close(fds[1]);
+ posix_spawnattr_destroy(&attrp);
posix_spawn_file_actions_destroy(&file_actions);
// Wait for daemonize to exit. This guarantees that the pid file exists.
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index cf91e12..04f7eab 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -642,7 +642,6 @@
AutoHandle proc;
};
-
int ExecuteDaemon(const string& exe,
const std::vector<string>& args_vector,
const std::map<string, EnvVarValue>& env,
@@ -650,6 +649,7 @@
const bool daemon_out_append,
const string& binaries_dir,
const string& server_dir,
+ const StartupOptions* options,
BlazeServerStartup** server_startup) {
wstring wdaemon_output;
string error;
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc
index 3d2cb48..4f3c5ca 100644
--- a/src/main/cpp/startup_options.cc
+++ b/src/main/cpp/startup_options.cc
@@ -17,6 +17,7 @@
#include <cstdio>
#include <cstdlib>
+#include <cstring>
#include "src/main/cpp/blaze_util.h"
#include "src/main/cpp/blaze_util_platform.h"
@@ -96,6 +97,9 @@
digest_function(),
idle_server_tasks(true),
original_startup_options_(std::vector<RcStartupFlag>()),
+#if defined(__APPLE__)
+ macos_qos_class(QOS_CLASS_DEFAULT),
+#endif
unlimit_coredumps(false) {
if (blaze::IsRunningWithinTest()) {
output_root = blaze_util::MakeAbsolute(blaze::GetPathEnv("TEST_TMPDIR"));
@@ -151,6 +155,7 @@
RegisterUnaryStartupFlag("invocation_policy");
RegisterUnaryStartupFlag("io_nice_level");
RegisterUnaryStartupFlag("install_base");
+ RegisterUnaryStartupFlag("macos_qos_class");
RegisterUnaryStartupFlag("max_idle_secs");
RegisterUnaryStartupFlag("output_base");
RegisterUnaryStartupFlag("output_user_root");
@@ -290,6 +295,36 @@
return blaze_exit_code::BAD_ARGV;
}
option_sources["max_idle_secs"] = rcfile;
+ } else if ((value = GetUnaryOption(arg, next_arg, "--macos_qos_class")) !=
+ NULL) {
+ // We parse the value of this flag on all platforms even if it is
+ // macOS-specific to ensure that rc files mentioning it are valid.
+ if (strcmp(value, "user-interactive") == 0) {
+#if defined(__APPLE__)
+ macos_qos_class = QOS_CLASS_USER_INTERACTIVE;
+#endif
+ } else if (strcmp(value, "user-initiated") == 0) {
+#if defined(__APPLE__)
+ macos_qos_class = QOS_CLASS_USER_INITIATED;
+#endif
+ } else if (strcmp(value, "default") == 0) {
+#if defined(__APPLE__)
+ macos_qos_class = QOS_CLASS_DEFAULT;
+#endif
+ } else if (strcmp(value, "utility") == 0) {
+#if defined(__APPLE__)
+ macos_qos_class = QOS_CLASS_UTILITY;
+#endif
+ } else if (strcmp(value, "background") == 0) {
+#if defined(__APPLE__)
+ macos_qos_class = QOS_CLASS_BACKGROUND;
+#endif
+ } else {
+ blaze_util::StringPrintf(
+ error, "Invalid argument to --macos_qos_class: '%s'.", value);
+ return blaze_exit_code::BAD_ARGV;
+ }
+ option_sources["macos_qos_class"] = rcfile;
} else if (GetNullaryOption(arg, "--shutdown_on_low_sys_mem")) {
shutdown_on_low_sys_mem = true;
option_sources["shutdown_on_low_sys_mem"] = rcfile;
diff --git a/src/main/cpp/startup_options.h b/src/main/cpp/startup_options.h
index 23105e3..168f212 100644
--- a/src/main/cpp/startup_options.h
+++ b/src/main/cpp/startup_options.h
@@ -14,6 +14,10 @@
#ifndef BAZEL_SRC_MAIN_CPP_STARTUP_OPTIONS_H_
#define BAZEL_SRC_MAIN_CPP_STARTUP_OPTIONS_H_
+#if defined(__APPLE__)
+#include <sys/qos.h>
+#endif
+
#include <map>
#include <memory>
#include <set>
@@ -310,6 +314,11 @@
// Whether to raise the soft coredump limit to the hard one or not.
bool unlimit_coredumps;
+#if defined(__APPLE__)
+ // The QoS class to apply to the Bazel server process.
+ qos_class_t macos_qos_class;
+#endif
+
protected:
// Constructor for subclasses only so that site-specific extensions of this
// class can override the product name. The product_name must be the
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
index 1c60aa7..d4de1f9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
@@ -483,4 +483,18 @@
+ " Bash-style is buggy, the Windows-style is correct. See"
+ " https://github.com/bazelbuild/bazel/issues/7122")
public boolean windowsStyleArgEscaping;
+
+ @Option(
+ name = "macos_qos_class",
+ defaultValue = "default", // Only for documentation; value is set and used by the client.
+ documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
+ effectTags = {
+ OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS,
+ },
+ help =
+ "Sets the QoS service class of the %{product} server when running on macOS. This "
+ + "flag has no effect on all other platforms but is supported to ensure rc files "
+ + "can be shared among them without changes. Possible values are: user-interactive, "
+ + "user-initiated, default, utility, and background.")
+ public String macosQosClass;
}
diff --git a/src/test/cpp/bazel_startup_options_test.cc b/src/test/cpp/bazel_startup_options_test.cc
index 27d692f..5f7cf4d 100644
--- a/src/test/cpp/bazel_startup_options_test.cc
+++ b/src/test/cpp/bazel_startup_options_test.cc
@@ -106,6 +106,7 @@
ExpectIsUnaryOption(options, "invocation_policy");
ExpectIsUnaryOption(options, "io_nice_level");
ExpectIsUnaryOption(options, "install_base");
+ ExpectIsUnaryOption(options, "macos_qos_class");
ExpectIsUnaryOption(options, "max_idle_secs");
ExpectIsUnaryOption(options, "output_base");
ExpectIsUnaryOption(options, "output_user_root");
diff --git a/src/test/shell/integration/client_test.sh b/src/test/shell/integration/client_test.sh
index 8d310cd..0ad9939 100755
--- a/src/test/shell/integration/client_test.sh
+++ b/src/test/shell/integration/client_test.sh
@@ -262,4 +262,20 @@
fi
}
+function test_macos_qos_class() {
+ for class in user-interactive user-initiated default utility background; do
+ bazel --macos_qos_class="${class}" info >"${TEST_log}" 2>&1 \
+ || fail "Unknown QoS class ${class}"
+ # On macOS it'd be nice to verify that the server is indeed running at the
+ # desired class... but this is very hard to do. Common utilities do not
+ # print the QoS level, and powermetrics (which requires root privileges)
+ # only reports it under load -- so an "info" command is insufficient and the
+ # real thing would be quite expensive.
+ done
+
+ bazel --macos_qos_class=foo >"${TEST_log}" 2>&1 \
+ && fail "Expected failure with invalid QoS class name"
+ expect_log "Invalid argument.*qos_class.*foo"
+}
+
run_suite "Tests of the bazel client."