libprocessgroup: Use cgroup.kill

By using cgroup.kill we don't need to read cgroup.procs at all for
SIGKILLs, which is more efficient and should help reduce CPU contention
and cgroup lock contention. Fallback to cgroup.procs if we encounter an
error trying to use cgroup.kill, but if cgroup.kill fails it's likely
that cgroup.procs will too.

Bug: 239829790
Change-Id: I44706faccfb7c4611b512a3642b913f06d30c1dc
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 7e27d75..4b49d09 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -56,6 +56,7 @@
 using namespace std::chrono_literals;
 
 #define PROCESSGROUP_CGROUP_PROCS_FILE "cgroup.procs"
+#define PROCESSGROUP_CGROUP_KILL_FILE "cgroup.kill"
 #define PROCESSGROUP_CGROUP_EVENTS_FILE "cgroup.events"
 
 bool CgroupsAvailable() {
@@ -77,6 +78,29 @@
     return true;
 }
 
+static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
+    return StringPrintf("%s/uid_%u", cgroup, uid);
+}
+
+static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
+    return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
+}
+
+static bool CgroupKillAvailable() {
+    static std::once_flag f;
+    static bool cgroup_kill_available = false;
+    std::call_once(f, []() {
+        std::string cg_kill;
+        CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg_kill);
+        // cgroup.kill is not on the root cgroup, so check a non-root cgroup that should always
+        // exist
+        cg_kill = ConvertUidToPath(cg_kill.c_str(), AID_ROOT) + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+        cgroup_kill_available = access(cg_kill.c_str(), F_OK) == 0;
+    });
+
+    return cgroup_kill_available;
+}
+
 static bool CgroupGetMemcgAppsPath(std::string* path) {
     CgroupController controller = CgroupMap::GetInstance().FindController("memory");
 
@@ -208,14 +232,6 @@
                                                        false);
 }
 
-static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
-    return StringPrintf("%s/uid_%u", cgroup, uid);
-}
-
-static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
-    return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
-}
-
 static int RemoveCgroup(const char* cgroup, uid_t uid, int pid) {
     auto path = ConvertUidPidToPath(cgroup, uid, pid);
     int ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
@@ -362,6 +378,20 @@
         CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
         cgroup_v2_path = ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
 
+        if (signal == SIGKILL && CgroupKillAvailable()) {
+            LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_KILL_FILE << " to SIGKILL "
+                         << cgroup_v2_path;
+            const std::string killfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+            if (WriteStringToFile("1", killfilepath)) {
+                return true;
+            } else {
+                PLOG(ERROR) << "Failed to write 1 to " << killfilepath;
+                // Fallback to cgroup.procs below
+            }
+        }
+
+        // Since cgroup.kill only sends SIGKILLs, we read cgroup.procs to find each process to
+        // signal individually. This is more costly than using cgroup.kill for SIGKILLs.
         LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_PROCS_FILE << " to signal (" << signal
                      << ") " << cgroup_v2_path;