arcvm: Enforce cfs_quota again after Pre-ANR condition is handled

For example, consider the following scenario:

1. ARCVM finishes booting.
2. ArcBootPhaseThrottle throttles the instance
3. Pre-ANR happens. ArcPowerThrottle unthrottles the instance
4. Pre-ANR is handled. ArcPowerThrottle throttles the instance

Currently, at #2, the quota is enforced but at #4 it is not because
of #3 (unthrottle). With this CL, the quota is enforced again at #4
by recognizing that the unthrottling at #3 is not by the user's
action.

BUG=b:205762950
TEST=new unit_tests

Change-Id: Ie29b7d38702575c592a90909e6b14e0aa4419aa7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3582887
Reviewed-by: Yury Khmel <khmel@chromium.org>
Commit-Queue: Yusuke Sato <yusukes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#991862}
diff --git a/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.cc b/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.cc
index 050b6b3..5c1ad23 100644
--- a/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.cc
+++ b/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.cc
@@ -28,6 +28,47 @@
 
 namespace {
 
+enum class UnthrottlingReason {
+  // Unthrottling ARC to make its boot faster.
+  kFasterBoot = 0,
+  // Unthrottling ARC to prevent ANRs from happening.
+  kPreAnr = 1,
+  // All others.
+  kOther = 2,
+};
+
+// Checks all the |observers| for active ones to find out the reason why the
+// instance is being unthrottled.
+// This function can only be called when the instance is being unthrottled.
+UnthrottlingReason GetUnthrottlingReason(
+    const std::vector<std::unique_ptr<chromeos::ThrottleObserver>>& observers) {
+  std::vector<chromeos::ThrottleObserver*> active_observers;
+
+  // Check which observer(s) are active.
+  for (const auto& observer : observers) {
+    if (observer->active())
+      active_observers.push_back(observer.get());
+  }
+
+  UnthrottlingReason result = UnthrottlingReason::kOther;
+  DCHECK(!active_observers.empty()) << "All observers are inactive";
+
+  for (const auto* active_observer : active_observers) {
+    if (active_observer->name() == kArcBootPhaseThrottleObserverName) {
+      result = UnthrottlingReason::kFasterBoot;
+    } else if (active_observer->name() == kArcPowerThrottleObserverName) {
+      result = UnthrottlingReason::kPreAnr;
+    } else {
+      // If an unknown observer is found, return kOther immediately.
+      return UnthrottlingReason::kOther;
+    }
+  }
+
+  // Note: If both ArcBootPhaseThrottleObserver and ArcPowerThrottleObserver are
+  // active, either kFasterBoot or kPreAnr is returned as a special case.
+  return result;
+}
+
 void OnSetArcVmCpuRestriction(
     absl::optional<vm_tools::concierge::SetVmCpuRestrictionResponse> response) {
   if (!response) {
@@ -208,6 +249,12 @@
     : ThrottleService(context),
       delegate_(std::make_unique<DefaultDelegateImpl>()),
       bridge_(bridge) {
+  // Note: When you add a new observer, consider modifying GetUnthrottlingReason
+  // so that the CPU quota enforcement feature continues to work as intended. In
+  // general, unthrottling that is done automatically without the user's action
+  // might have to be listed in the UnthrottlingReason enum class. See the
+  // comment in ArcInstanceThrottle::ThrottleInstance too.
+
   AddObserver(std::make_unique<ArcActiveWindowThrottleObserver>());
   AddObserver(std::make_unique<ArcAppLaunchThrottleObserver>());
   AddObserver(std::make_unique<ArcBootPhaseThrottleObserver>());
@@ -219,6 +266,7 @@
   // This one is controlled by chromeos::ArcPowerControlHandler.
   AddObserver(std::make_unique<ash::ThrottleObserver>(
       kChromeArcPowerControlPageObserver));
+
   StartObservers();
   DCHECK(bridge_);
   bridge_->power()->AddObserver(this);
@@ -245,16 +293,50 @@
   // Check if enforcing quota is possible
   bool use_quota = false;
 
+  if (!should_throttle && !never_enforce_quota_) {
+    // We're unthrottling the instance. Figure out why.
+    switch (GetUnthrottlingReason(observers())) {
+      case UnthrottlingReason::kFasterBoot:
+        DVLOG(2) << "Unthrottling for faster boot. Quota is still applicable.";
+        break;
+      case UnthrottlingReason::kPreAnr:
+        DVLOG(2)
+            << "Unthrottling for preventing ANRs. Quota is still applicable.";
+        break;
+      case UnthrottlingReason::kOther:
+        // ARC is unthrottled by a user action. After such an event, the quota
+        // should never be applied.
+        DVLOG(2) << "Unthrottling because of a user action. Quota is no longer "
+                    "applicable.";
+        never_enforce_quota_ = true;
+        break;
+    }
+    // Note on why other throttle observers that don't monitor the user's action
+    // can be classified as |kOther| and can disable quota:
+    //
+    // * ArcSwitchThrottleObserver:
+    //   This is for disabling throttling for testing. If the observer gets
+    //   activated, the quota shouldn't be applied either.
+    //
+    // * ArcKioskModeThrottleObserver:
+    //   If this gets activated, ARC will be used for Kiosk. There's no point in
+    //   applying quota since ARC will always be foreground.
+    //
+    // * ArcProvisioningThrottleObserver:
+    //   If this gets activated, the provisioning is ongoing. The quota
+    //   shouldn't be applied to make provisioning failures less likely to
+    //   happen.
+  }
+
   const absl::optional<bool>& arc_is_booting =
       GetBootObserver()->arc_is_booting();
   const bool arc_has_booted = (arc_is_booting && !*arc_is_booting);
   const bool is_throttling = (cpu_restriction_state ==
                               CpuRestrictionState::CPU_RESTRICTION_BACKGROUND);
 
-  if (arc_has_booted && !quota_ever_enforced_ && is_throttling) {
+  if (arc_has_booted && !never_enforce_quota_ && is_throttling) {
     // TODO(yusukes): Do not use quota when Android VPN is in use.
     use_quota = true;
-    quota_ever_enforced_ = true;
     DVLOG(2) << "Enforcing cfs_quota";
   }
   delegate_->SetCpuRestriction(cpu_restriction_state, use_quota);
diff --git a/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.h b/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.h
index 40b89a0..b5e3650 100644
--- a/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.h
+++ b/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.h
@@ -92,9 +92,9 @@
   ArcBootPhaseThrottleObserver* GetBootObserver();
 
   std::unique_ptr<Delegate> delegate_;
-  // True if CPU_RESTRICTION_BACKGROUND_WITH_CFS_QUOTA_ENFORCED has ever been
+  // True if CPU_RESTRICTION_BACKGROUND_WITH_CFS_QUOTA_ENFORCED should never be
   // used.
-  bool quota_ever_enforced_ = false;
+  bool never_enforce_quota_ = false;
 
   // Owned by ArcServiceManager.
   ArcBridgeService* const bridge_;
diff --git a/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle_unittest.cc b/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle_unittest.cc
index e1e0dfbad..3c71c66c 100644
--- a/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle_unittest.cc
+++ b/chrome/browser/ash/arc/instance_throttle/arc_instance_throttle_unittest.cc
@@ -24,6 +24,7 @@
 #include "base/command_line.h"
 #include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
 #include "chrome/browser/ash/arc/instance_throttle/arc_boot_phase_throttle_observer.h"
+#include "chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
 #include "chrome/browser/ash/throttle_observer.h"
@@ -145,10 +146,44 @@
     return arc_instance_throttle_;
   }
 
+  // Returns an observer that will be classified as |kOther| in
+  // GetUnthrottlingReason().
   ash::ThrottleObserver* GetThrottleObserver() {
     const auto& observers = arc_instance_throttle()->observers_for_testing();
     DCHECK(!observers.empty());
-    return observers[0].get();
+    for (const auto& observer : observers) {
+      // This must be in sync with GetUnthrottlingReason().
+      if (observer->name() == kArcBootPhaseThrottleObserverName)
+        continue;
+      else if (observer->name() == kArcPowerThrottleObserverName)
+        continue;
+      else
+        return observer.get();
+    }
+    NOTREACHED();
+    return nullptr;
+  }
+
+  ash::ThrottleObserver* GetArcBootPhaseThrottleObserver() {
+    const auto& observers = arc_instance_throttle()->observers_for_testing();
+    DCHECK(!observers.empty());
+    for (const auto& observer : observers) {
+      if (observer->name() == kArcBootPhaseThrottleObserverName)
+        return observer.get();
+    }
+    NOTREACHED();
+    return nullptr;
+  }
+
+  ash::ThrottleObserver* GetArcPowerThrottleObserver() {
+    const auto& observers = arc_instance_throttle()->observers_for_testing();
+    DCHECK(!observers.empty());
+    for (const auto& observer : observers) {
+      if (observer->name() == kArcPowerThrottleObserverName)
+        return observer.get();
+    }
+    NOTREACHED();
+    return nullptr;
   }
 
   FakePowerInstance* power_instance() { return power_instance_.get(); }
@@ -235,16 +270,18 @@
   EXPECT_EQ(1U, disable_cpu_restriction_counter());
 }
 
-// Tests that ArcInstanceThrottle enforces quota only once after ARC
-// boot.
+// Tests that ArcInstanceThrottle enforces quota only until unthrottled via a
+// user action.
 TEST_F(ArcInstanceThrottleTest, TestThrottleInstanceQuotaEnforcement) {
   // While ARC is booting, quota shouldn't be enforced.
-  GetThrottleObserver()->SetActive(false);
+  GetArcBootPhaseThrottleObserver()->SetActive(false);
   EXPECT_EQ(1U, enable_cpu_restriction_counter());
   EXPECT_EQ(0U, use_quota_counter());
   EXPECT_EQ(0U, disable_cpu_restriction_counter());
 
-  GetThrottleObserver()->SetActive(true);
+  // Note: Use ArcBootPhaseThrottleObserver so that quota will still be
+  // applicable.
+  GetArcBootPhaseThrottleObserver()->SetActive(true);
   EXPECT_EQ(1U, enable_cpu_restriction_counter());
   EXPECT_EQ(0U, use_quota_counter());
   EXPECT_EQ(1U, disable_cpu_restriction_counter());
@@ -254,12 +291,12 @@
 
   // Since 10 seconds haven't passed since the mojom connection, quota is
   // still not enforced.
-  GetThrottleObserver()->SetActive(false);
+  GetArcBootPhaseThrottleObserver()->SetActive(false);
   EXPECT_EQ(2U, enable_cpu_restriction_counter());
   EXPECT_EQ(0U, use_quota_counter());
   EXPECT_EQ(1U, disable_cpu_restriction_counter());
 
-  GetThrottleObserver()->SetActive(true);
+  GetArcBootPhaseThrottleObserver()->SetActive(true);
   EXPECT_EQ(2U, enable_cpu_restriction_counter());
   EXPECT_EQ(0U, use_quota_counter());
   EXPECT_EQ(2U, disable_cpu_restriction_counter());
@@ -269,21 +306,33 @@
       ArcBootPhaseThrottleObserver::GetThrottleDelayForTesting());
 
   // Now quota is enforced,
-  GetThrottleObserver()->SetActive(false);
+  GetArcBootPhaseThrottleObserver()->SetActive(false);
   EXPECT_EQ(3U, enable_cpu_restriction_counter());
   EXPECT_EQ(1U, use_quota_counter());
   EXPECT_EQ(2U, disable_cpu_restriction_counter());
 
-  // Quota is enforced only once.
-  GetThrottleObserver()->SetActive(true);
+  // Unthrottle the instance because of pre-ANR.
+  GetArcPowerThrottleObserver()->SetActive(true);
   EXPECT_EQ(3U, enable_cpu_restriction_counter());
   EXPECT_EQ(1U, use_quota_counter());
   EXPECT_EQ(3U, disable_cpu_restriction_counter());
 
-  GetThrottleObserver()->SetActive(false);
+  // Quota is enforced again once pre-ANR is handled.
+  GetArcPowerThrottleObserver()->SetActive(false);
   EXPECT_EQ(4U, enable_cpu_restriction_counter());
-  EXPECT_EQ(1U, use_quota_counter());
+  EXPECT_EQ(2U, use_quota_counter());
   EXPECT_EQ(3U, disable_cpu_restriction_counter());
+
+  // Quota is not enforced once unthrottled via a user action.
+  GetThrottleObserver()->SetActive(true);
+  EXPECT_EQ(4U, enable_cpu_restriction_counter());
+  EXPECT_EQ(2U, use_quota_counter());
+  EXPECT_EQ(4U, disable_cpu_restriction_counter());
+
+  GetThrottleObserver()->SetActive(false);
+  EXPECT_EQ(5U, enable_cpu_restriction_counter());
+  EXPECT_EQ(2U, use_quota_counter());
+  EXPECT_EQ(4U, disable_cpu_restriction_counter());
 }
 
 // Tests that power instance is correctly notified.
diff --git a/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.cc b/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.cc
index 903fb2d..fefc7c9 100644
--- a/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.cc
+++ b/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.cc
@@ -17,7 +17,7 @@
 }  // namespace
 
 ArcPowerThrottleObserver::ArcPowerThrottleObserver()
-    : ThrottleObserver("ArcPower") {}
+    : ThrottleObserver(kArcPowerThrottleObserverName) {}
 
 ArcPowerThrottleObserver::~ArcPowerThrottleObserver() = default;
 
diff --git a/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.h b/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.h
index 0765559..89d0a09a 100644
--- a/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.h
+++ b/chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.h
@@ -11,6 +11,8 @@
 
 namespace arc {
 
+constexpr char kArcPowerThrottleObserverName[] = "ArcPower";
+
 // Listens ARC power events and lifts CPU throttling when needed.
 class ArcPowerThrottleObserver : public chromeos::ThrottleObserver,
                                  public ArcPowerBridge::Observer {