Configure backing device max_ratio for FUSE filesystems. am: a485006ab1 am: 5ed648d098 am: 6589ae36c3

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/vold/+/12024019

Change-Id: I20555c69735747460b0ffe137a116b566ef5b158
diff --git a/Utils.cpp b/Utils.cpp
index 6208efd..a9b7440 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -1386,22 +1386,54 @@
     return OK;
 }
 
+// Gets the sysfs path for parameters of the backing device info (bdi)
+static std::string getBdiPathForMount(const std::string& mount) {
+    // First figure out MAJOR:MINOR of mount. Simplest way is to stat the path.
+    struct stat info;
+    if (stat(mount.c_str(), &info) != 0) {
+        PLOG(ERROR) << "Failed to stat " << mount;
+        return "";
+    }
+    unsigned int maj = major(info.st_dev);
+    unsigned int min = minor(info.st_dev);
+
+    return StringPrintf("/sys/class/bdi/%u:%u", maj, min);
+}
+
+// Configures max_ratio for the FUSE filesystem.
+void ConfigureMaxDirtyRatioForFuse(const std::string& fuse_mount, unsigned int max_ratio) {
+    LOG(INFO) << "Configuring max_ratio of " << fuse_mount << " fuse filesystem to " << max_ratio;
+    if (max_ratio > 100) {
+        LOG(ERROR) << "Invalid max_ratio: " << max_ratio;
+        return;
+    }
+    std::string fuseBdiPath = getBdiPathForMount(fuse_mount);
+    if (fuseBdiPath == "") {
+        return;
+    }
+    std::string max_ratio_file = StringPrintf("%s/max_ratio", fuseBdiPath.c_str());
+    unique_fd fd(TEMP_FAILURE_RETRY(open(max_ratio_file.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (fd.get() == -1) {
+        PLOG(ERROR) << "Failed to open " << max_ratio_file;
+        return;
+    }
+    LOG(INFO) << "Writing " << max_ratio << " to " << max_ratio_file;
+    if (!WriteStringToFd(std::to_string(max_ratio), fd)) {
+        PLOG(ERROR) << "Failed to write to " << max_ratio_file;
+    }
+}
+
 // Configures read ahead property of the fuse filesystem with the mount point |fuse_mount| by
 // writing |read_ahead_kb| to the /sys/class/bdi/MAJOR:MINOR/read_ahead_kb.
 void ConfigureReadAheadForFuse(const std::string& fuse_mount, size_t read_ahead_kb) {
     LOG(INFO) << "Configuring read_ahead of " << fuse_mount << " fuse filesystem to "
               << read_ahead_kb << "kb";
-    // First figure out MAJOR:MINOR of fuse_mount. Simplest way is to stat the path.
-    struct stat info;
-    if (stat(fuse_mount.c_str(), &info) != 0) {
-        PLOG(ERROR) << "Failed to stat " << fuse_mount;
+    std::string fuseBdiPath = getBdiPathForMount(fuse_mount);
+    if (fuseBdiPath == "") {
         return;
     }
-    unsigned int maj = major(info.st_dev);
-    unsigned int min = minor(info.st_dev);
-    LOG(INFO) << fuse_mount << " has major:minor " << maj << ":" << min;
-    // We found major:minor of our filesystem, time to configure read ahead!
-    std::string read_ahead_file = StringPrintf("/sys/class/bdi/%u:%u/read_ahead_kb", maj, min);
+    // We found the bdi path for our filesystem, time to configure read ahead!
+    std::string read_ahead_file = StringPrintf("%s/read_ahead_kb", fuseBdiPath.c_str());
     unique_fd fd(TEMP_FAILURE_RETRY(open(read_ahead_file.c_str(), O_WRONLY | O_CLOEXEC)));
     if (fd.get() == -1) {
         PLOG(ERROR) << "Failed to open " << read_ahead_file;
diff --git a/Utils.h b/Utils.h
index a1d34b8..04cbac4 100644
--- a/Utils.h
+++ b/Utils.h
@@ -176,6 +176,8 @@
 
 bool writeStringToFile(const std::string& payload, const std::string& filename);
 
+void ConfigureMaxDirtyRatioForFuse(const std::string& fuse_mount, unsigned int max_ratio);
+
 void ConfigureReadAheadForFuse(const std::string& fuse_mount, size_t read_ahead_kb);
 
 status_t MountUserFuse(userid_t user_id, const std::string& absolute_lower_path,
diff --git a/model/EmulatedVolume.cpp b/model/EmulatedVolume.cpp
index 26d9582..db93bc2 100644
--- a/model/EmulatedVolume.cpp
+++ b/model/EmulatedVolume.cpp
@@ -404,6 +404,27 @@
 
         ConfigureReadAheadForFuse(GetFuseMountPathForUser(user_id, label), 256u);
 
+        // By default, FUSE has a max_dirty ratio of 1%. This means that out of
+        // all dirty pages in the system, only 1% is allowed to belong to any
+        // FUSE filesystem. The reason this is in place is that FUSE
+        // filesystems shouldn't be trusted by default; a FUSE filesystem could
+        // take up say 100% of dirty pages, and subsequently refuse to write
+        // them back to storage.  The kernel will then apply rate-limiting, and
+        // block other tasks from writing.  For this particular FUSE filesystem
+        // however, we trust the implementation, because it is a part of the
+        // Android platform. So use the default ratio of 100%.
+        //
+        // The reason we're setting this is that there's a suspicion that the
+        // kernel starts rate-limiting the FUSE filesystem under extreme
+        // memory pressure scenarios. While the kernel will only rate limit if
+        // the writeback can't keep up with the write rate, under extreme
+        // memory pressure the write rate may dip as well, in which case FUSE
+        // writes to a 1% max_ratio filesystem are throttled to an extreme amount.
+        //
+        // To prevent this, just give FUSE 40% max_ratio, meaning it can take
+        // up to 40% of all dirty pages in the system.
+        ConfigureMaxDirtyRatioForFuse(GetFuseMountPathForUser(user_id, label), 40u);
+
         // All mounts where successful, disable scope guards
         sdcardfs_guard.Disable();
         fuse_guard.Disable();
diff --git a/model/PublicVolume.cpp b/model/PublicVolume.cpp
index 9ca782b..d40e3e3 100644
--- a/model/PublicVolume.cpp
+++ b/model/PublicVolume.cpp
@@ -255,6 +255,9 @@
         }
 
         ConfigureReadAheadForFuse(GetFuseMountPathForUser(user_id, stableName), 256u);
+
+        // See comment in model/EmulatedVolume.cpp
+        ConfigureMaxDirtyRatioForFuse(GetFuseMountPathForUser(user_id, stableName), 40u);
     }
 
     return OK;