Merge "Run module code only after KeyMints receive module info" into main
diff --git a/init/oneshot_on_test.cpp b/init/oneshot_on_test.cpp
index 650f065..b039ac2 100644
--- a/init/oneshot_on_test.cpp
+++ b/init/oneshot_on_test.cpp
@@ -39,11 +39,13 @@
// Bootanim exits quickly when the device is fully booted, so check that it goes back to the
// 'restarting' state that non-oneshot services enter once they've restarted.
- EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "restarting", 10s));
+ EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "restarting", 10s))
+ << "Value is: " << GetProperty("init.svc.bootanim", "");
SetProperty("ctl.oneshot_on", "bootanim");
SetProperty("ctl.start", "bootanim");
// Now that oneshot is enabled again, bootanim should transition into the 'stopped' state.
- EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "stopped", 10s));
+ EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "stopped", 10s))
+ << "Value is: " << GetProperty("init.svc.bootanim", "");
}
diff --git a/init/property_service.cpp b/init/property_service.cpp
index f2606e3..31af94e 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -595,7 +595,7 @@
}
static void handle_property_set_fd(int fd) {
- static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
+ static constexpr uint32_t kDefaultSocketTimeout = 5000; /* ms */
int s = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 1e76e76..6725acc 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -7,7 +7,6 @@
module_type: "cc_defaults",
config_namespace: "ANDROID",
bool_variables: [
- "memcg_v2_force_enabled",
"cgroup_v2_sys_app_isolation",
],
properties: [
@@ -19,11 +18,6 @@
name: "libprocessgroup_build_flags_cc",
cpp_std: "gnu++23",
soong_config_variables: {
- memcg_v2_force_enabled: {
- cflags: [
- "-DMEMCG_V2_FORCE_ENABLED=true",
- ],
- },
cgroup_v2_sys_app_isolation: {
cflags: [
"-DCGROUP_V2_SYS_APP_ISOLATION=true",
diff --git a/libprocessgroup/build_flags.h b/libprocessgroup/build_flags.h
index bc3e7df..d0948c3 100644
--- a/libprocessgroup/build_flags.h
+++ b/libprocessgroup/build_flags.h
@@ -16,20 +16,12 @@
#pragma once
-#ifndef MEMCG_V2_FORCE_ENABLED
-#define MEMCG_V2_FORCE_ENABLED false
-#endif
-
#ifndef CGROUP_V2_SYS_APP_ISOLATION
#define CGROUP_V2_SYS_APP_ISOLATION false
#endif
namespace android::libprocessgroup_flags {
-inline consteval bool force_memcg_v2() {
- return MEMCG_V2_FORCE_ENABLED;
-}
-
inline consteval bool cgroup_v2_sys_app_isolation() {
return CGROUP_V2_SYS_APP_ISOLATION;
}
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index c4e1fb6..0d1739e 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -27,9 +27,6 @@
#include <sys/types.h>
#include <unistd.h>
-#include <optional>
-
-#include <android-base/file.h>
#include <android-base/logging.h>
#include <processgroup/cgroup_descriptor.h>
#include <processgroup/processgroup.h>
@@ -260,39 +257,6 @@
controller_.set_flags(flags);
}
-static std::optional<bool> MGLRUDisabled() {
- const std::string file_name = "/sys/kernel/mm/lru_gen/enabled";
- std::string content;
- if (!android::base::ReadFileToString(file_name, &content)) {
- PLOG(ERROR) << "Failed to read MGLRU state from " << file_name;
- return {};
- }
-
- return content == "0x0000";
-}
-
-static std::optional<bool> MEMCGDisabled(const CgroupDescriptorMap& descriptors) {
- std::string cgroup_v2_root = CGROUP_V2_ROOT_DEFAULT;
- const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
- if (it == descriptors.end()) {
- LOG(WARNING) << "No Cgroups2 path found in cgroups.json. Vendor has modified Android, and "
- << "kernel memory use will be higher than intended.";
- } else if (it->second.controller()->path() != cgroup_v2_root) {
- cgroup_v2_root = it->second.controller()->path();
- }
-
- const std::string file_name = cgroup_v2_root + "/cgroup.controllers";
- std::string content;
- if (!android::base::ReadFileToString(file_name, &content)) {
- PLOG(ERROR) << "Failed to read cgroup controllers from " << file_name;
- return {};
- }
-
- // If we've forced memcg to v2 and it's not available, then it could only have been disabled
- // on the kernel command line (GKI sets CONFIG_MEMCG).
- return content.find("memory") == std::string::npos;
-}
-
static bool CreateV2SubHierarchy(const std::string& path, const CgroupDescriptorMap& descriptors) {
const auto cgv2_iter = descriptors.find(CGROUPV2_HIERARCHY_NAME);
if (cgv2_iter == descriptors.end()) return false;
@@ -335,17 +299,6 @@
}
}
- if (android::libprocessgroup_flags::force_memcg_v2()) {
- if (MGLRUDisabled().value_or(false)) {
- LOG(WARNING) << "Memcg forced to v2 hierarchy with MGLRU disabled! "
- << "Global reclaim performance will suffer.";
- }
- if (MEMCGDisabled(descriptors).value_or(false)) {
- LOG(WARNING) << "Memcg forced to v2 hierarchy while memcg is disabled by kernel "
- << "command line!";
- }
- }
-
// System / app isolation.
// This really belongs in early-init in init.rc, but we cannot use the flag there.
if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation()) {
diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp
index 1c74d4e..266a53f 100644
--- a/libprocessgroup/util/Android.bp
+++ b/libprocessgroup/util/Android.bp
@@ -21,6 +21,7 @@
cc_library_static {
name: "libprocessgroup_util",
+ cpp_std: "gnu++23",
vendor_available: true,
product_available: true,
ramdisk_available: true,
@@ -47,7 +48,6 @@
static_libs: [
"libjsoncpp",
],
- defaults: ["libprocessgroup_build_flags_cc"],
}
cc_test {
diff --git a/libprocessgroup/util/util.cpp b/libprocessgroup/util/util.cpp
index 1401675..c772bc5 100644
--- a/libprocessgroup/util/util.cpp
+++ b/libprocessgroup/util/util.cpp
@@ -111,7 +111,6 @@
}
bool ReadDescriptorsFromFile(const std::string& file_name, CgroupDescriptorMap* descriptors) {
- static constexpr bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
std::vector<CgroupDescriptor> result;
std::string json_doc;
@@ -133,14 +132,10 @@
const Json::Value& cgroups = root["Cgroups"];
for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
std::string name = cgroups[i]["Controller"].asString();
-
- if (force_memcg_v2 && name == "memory") continue;
-
MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
}
}
- bool memcgv2_present = false;
std::string root_path;
if (root.isMember("Cgroups2")) {
const Json::Value& cgroups2 = root["Cgroups2"];
@@ -150,24 +145,10 @@
const Json::Value& childGroups = cgroups2["Controllers"];
for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
std::string name = childGroups[i]["Controller"].asString();
-
- if (force_memcg_v2 && name == "memory") memcgv2_present = true;
-
MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
}
}
- if (force_memcg_v2 && !memcgv2_present) {
- LOG(INFO) << "Forcing memcg to v2 hierarchy";
- Json::Value memcgv2;
- memcgv2["Controller"] = "memory";
- memcgv2["NeedsActivation"] = true;
- memcgv2["Path"] = ".";
- memcgv2["Optional"] = true; // In case of cgroup_disabled=memory, so we can still boot
- MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
- root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
- }
-
return true;
}
diff --git a/libprocessgroup/vts/Android.bp b/libprocessgroup/vts/Android.bp
new file mode 100644
index 0000000..1ec49a4
--- /dev/null
+++ b/libprocessgroup/vts/Android.bp
@@ -0,0 +1,15 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "vts_libprocessgroup",
+ srcs: ["vts_libprocessgroup.cpp"],
+ shared_libs: ["libbase"],
+ static_libs: ["libgmock"],
+ require_root: true,
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/libprocessgroup/vts/vts_libprocessgroup.cpp b/libprocessgroup/vts/vts_libprocessgroup.cpp
new file mode 100644
index 0000000..af0b0af
--- /dev/null
+++ b/libprocessgroup/vts/vts_libprocessgroup.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cerrno>
+#include <chrono>
+#include <cstdio>
+#include <filesystem>
+#include <future>
+#include <iostream>
+#include <optional>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+using android::base::ReadFileToString;
+using android::base::Split;
+using android::base::WriteStringToFile;
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+const std::string CGROUP_V2_ROOT_PATH = "/sys/fs/cgroup";
+
+std::optional<bool> isMemcgV2Enabled() {
+ if (std::string proc_cgroups; ReadFileToString("/proc/cgroups", &proc_cgroups)) {
+ const std::vector<std::string> lines = Split(proc_cgroups, "\n");
+ for (const std::string& line : lines) {
+ if (line.starts_with("memory")) {
+ const bool enabled = line.back() == '1';
+ if (!enabled) return false;
+
+ const std::vector<std::string> memcg_tokens = Split(line, "\t");
+ return memcg_tokens[1] == "0"; // 0 == default hierarchy == v2
+ }
+ }
+ // We know for sure it's not enabled, either because it is mounted as v1 (cgroups.json
+ // override) which would be detected above, or because it was intentionally disabled via
+ // kernel command line (cgroup_disable=memory), or because it's not built in to the kernel
+ // (CONFIG_MEMCG is not set).
+ return false;
+ }
+
+ // Problems accessing /proc/cgroups (sepolicy?) Try checking the root cgroup.controllers file.
+ perror("Warning: Could not read /proc/cgroups");
+ if (std::string controllers;
+ ReadFileToString(CGROUP_V2_ROOT_PATH + "/cgroup.controllers", &controllers)) {
+ return controllers.find("memory") != std::string::npos;
+ }
+
+ std::cerr << "Error: Could not read " << CGROUP_V2_ROOT_PATH
+ << "/cgroup.controllers: " << std::strerror(errno) << std::endl;
+ return std::nullopt;
+}
+
+std::optional<bool> checkRootSubtreeState() {
+ if (std::string controllers;
+ ReadFileToString(CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control", &controllers)) {
+ return controllers.find("memory") != std::string::npos;
+ }
+ std::cerr << "Error: Could not read " << CGROUP_V2_ROOT_PATH
+ << "/cgroup.subtree_control: " << std::strerror(errno) << std::endl;
+ return std::nullopt;
+}
+
+} // anonymous namespace
+
+
+class MemcgV2Test : public testing::Test {
+ protected:
+ void SetUp() override {
+ std::optional<bool> memcgV2Enabled = isMemcgV2Enabled();
+ ASSERT_NE(memcgV2Enabled, std::nullopt);
+ if (!*memcgV2Enabled) GTEST_SKIP() << "Memcg v2 not enabled";
+ }
+};
+
+class MemcgV2SubdirTest : public testing::Test {
+ protected:
+ std::optional<std::string> mRandDir;
+
+ void SetUp() override {
+ std::optional<bool> memcgV2Enabled = isMemcgV2Enabled();
+ ASSERT_NE(memcgV2Enabled, std::nullopt);
+ if (!*memcgV2Enabled) GTEST_SKIP() << "Memcg v2 not enabled";
+
+ mRootSubtreeState = checkRootSubtreeState();
+ ASSERT_NE(mRootSubtreeState, std::nullopt);
+
+ if (!*mRootSubtreeState) {
+ ASSERT_TRUE(
+ WriteStringToFile("+memory", CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control"))
+ << "Could not enable memcg under root: " << std::strerror(errno);
+ }
+
+ // Make a new, temporary, randomly-named v2 cgroup in which we will attempt to activate
+ // memcg
+ std::random_device rd;
+ std::uniform_int_distribution dist(static_cast<int>('A'), static_cast<int>('Z'));
+ std::string randName = CGROUP_V2_ROOT_PATH + "/vts_libprocessgroup.";
+ for (int i = 0; i < 10; ++i) randName.append(1, static_cast<char>(dist(rd)));
+ ASSERT_TRUE(std::filesystem::create_directory(randName));
+ mRandDir = randName; // For cleanup in TearDown
+
+ std::string subtree_controllers;
+ ASSERT_TRUE(ReadFileToString(*mRandDir + "/cgroup.controllers", &subtree_controllers));
+ ASSERT_NE(subtree_controllers.find("memory"), std::string::npos)
+ << "Memcg was not activated in child cgroup";
+ }
+
+ void TearDown() override {
+ if (mRandDir) {
+ if (!std::filesystem::remove(*mRandDir)) {
+ std::cerr << "Could not remove temporary memcg v2 test directory" << std::endl;
+ }
+ }
+
+ if (!*mRootSubtreeState) {
+ if (!WriteStringToFile("-memory", CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control")) {
+ std::cerr << "Could not disable memcg under root: " << std::strerror(errno)
+ << std::endl;
+ }
+ }
+ }
+
+ private:
+ std::optional<bool> mRootSubtreeState;
+};
+
+
+TEST_F(MemcgV2SubdirTest, CanActivateMemcgV2Subtree) {
+ ASSERT_TRUE(WriteStringToFile("+memory", *mRandDir + "/cgroup.subtree_control"))
+ << "Could not enable memcg under child cgroup subtree";
+}
+
+// Test for fix: mm: memcg: use larger batches for proactive reclaim
+// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=287d5fedb377ddc232b216b882723305b27ae31a
+TEST_F(MemcgV2Test, ProactiveReclaimDoesntTakeForever) {
+ // Not all kernels have memory.reclaim
+ const std::filesystem::path reclaim(CGROUP_V2_ROOT_PATH + "/memory.reclaim");
+ if (!std::filesystem::exists(reclaim)) GTEST_SKIP() << "memory.reclaim not found";
+
+ // Use the total device memory as the amount to reclaim
+ const long numPages = sysconf(_SC_PHYS_PAGES);
+ const long pageSize = sysconf(_SC_PAGE_SIZE);
+ ASSERT_GT(numPages, 0);
+ ASSERT_GT(pageSize, 0);
+ const unsigned long long totalMem =
+ static_cast<unsigned long long>(numPages) * static_cast<unsigned long long>(pageSize);
+
+ auto fut = std::async(std::launch::async,
+ [&]() { WriteStringToFile(std::to_string(totalMem), reclaim); });
+
+ // This is a test for completion within the timeout. The command is likely to "fail" since we
+ // are asking to reclaim all device memory.
+ ASSERT_NE(fut.wait_for(std::chrono::seconds(20)), std::future_status::timeout);
+}