[go: nahoru, domu]

Updater: Periodically check the server for idleness and shut down if so.

Fixed: 1451862
Change-Id: I3e81ea30fd8be791fc220ae6c6269f2f7274d980
Low-Coverage-Reason: Covered by IT.
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4602269
Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com>
Auto-Submit: Joshua Pawlicki <waffles@chromium.org>
Commit-Queue: Sorin Jianu <sorin@chromium.org>
Reviewed-by: Sorin Jianu <sorin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1156269}
diff --git a/chrome/updater/app/app_server.cc b/chrome/updater/app/app_server.cc
index a40514fd..601d52c 100644
--- a/chrome/updater/app/app_server.cc
+++ b/chrome/updater/app/app_server.cc
@@ -14,6 +14,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/process/launch.h"
 #include "base/process/process.h"
+#include "base/time/time.h"
 #include "base/version.h"
 #include "chrome/updater/app/app_utils.h"
 #include "chrome/updater/configurator.h"
@@ -122,7 +123,38 @@
                         base::MakeRefCounted<UpdateServiceImpl>(config_));
 }
 
+void AppServer::TaskStarted() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ++tasks_running_;
+}
+
+void AppServer::TaskCompleted() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](scoped_refptr<AppServer> server) {
+            --(server->tasks_running_);
+            server->OnDelayedTaskComplete();
+            if (server->IsIdle() && server->ShutdownIfIdleAfterTask()) {
+              server->Shutdown(0);
+            }
+          },
+          base::WrapRefCounted(this)),
+      external_constants()->ServerKeepAliveTime());
+}
+
+bool AppServer::IsIdle() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return tasks_running_ == 0;
+}
+
 void AppServer::Uninitialize() {
+  // Simply stopping the timer does not destroy its task. The task holds a
+  // refcount to this AppServer; therefore the task must be replaced and then
+  // the timer stopped.
+  hang_timer_.Start(FROM_HERE, base::Minutes(1), base::DoNothing());
+  hang_timer_.Stop();
   if (prefs_) {
     PrefsCommitPendingWrites(prefs_->GetPrefService());
   }
@@ -168,6 +200,14 @@
 
 void AppServer::FirstTaskRun() {
   std::move(first_task_).Run();
+  hang_timer_.Start(FROM_HERE, external_constants_->IdleCheckPeriod(),
+                    base::BindRepeating(
+                        [](scoped_refptr<AppServer> server) {
+                          if (server->IsIdle()) {
+                            server->Shutdown(kErrorIdle);
+                          }
+                        },
+                        base::WrapRefCounted(this)));
 }
 
 bool AppServer::SwapVersions(GlobalPrefs* global_prefs) {
diff --git a/chrome/updater/app/app_server.h b/chrome/updater/app/app_server.h
index 77fc767..ee8d9cd 100644
--- a/chrome/updater/app/app_server.h
+++ b/chrome/updater/app/app_server.h
@@ -7,6 +7,8 @@
 
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
 #include "chrome/updater/app/app.h"
 #include "chrome/updater/configurator.h"
 #include "chrome/updater/external_constants.h"
@@ -42,9 +44,17 @@
 
   scoped_refptr<Configurator> config() const { return config_; }
 
+  void TaskStarted();
+  void TaskCompleted();
+
+  // Returns whether the process is (or recently was) idle.
+  bool IsIdle();
+
   // Overrides of App.
   void Uninitialize() override;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
  private:
   // Overrides of App.
   void Initialize() final;
@@ -68,6 +78,13 @@
   // Uninstalls this candidate version of the updater.
   virtual void UninstallSelf() = 0;
 
+  // If true, this server will shut itself down after being idle for a period
+  // after completing a task.
+  virtual bool ShutdownIfIdleAfterTask() = 0;
+
+  // The server will call this method a short time after completing a task.
+  virtual void OnDelayedTaskComplete() = 0;
+
   // As part of initialization, an AppServer must do a mode check to determine
   // what mode of operation it should continue in. Possible modes include:
   //  - Qualify: this candidate is not yet qualified or active.
@@ -87,6 +104,7 @@
   scoped_refptr<ExternalConstants> external_constants_;
   scoped_refptr<UpdaterPrefs> prefs_;
   scoped_refptr<Configurator> config_;
+  base::RepeatingTimer hang_timer_;
 
   // If true, this version of the updater should uninstall itself during
   // shutdown.
@@ -94,6 +112,9 @@
 
   // The number of times the server has started, as read from global prefs.
   int server_starts_ = 0;
+
+  // The number of currently running tasks.
+  int tasks_running_ = 0;
 };
 
 scoped_refptr<App> AppServerInstance();
diff --git a/chrome/updater/app/app_server_unittest.cc b/chrome/updater/app/app_server_unittest.cc
index ccc63d60..470ecab 100644
--- a/chrome/updater/app/app_server_unittest.cc
+++ b/chrome/updater/app/app_server_unittest.cc
@@ -53,6 +53,8 @@
               (base::RepeatingCallback<void(const RegistrationRequest&)>),
               (override));
   MOCK_METHOD(void, UninstallSelf, (), (override));
+  MOCK_METHOD(bool, ShutdownIfIdleAfterTask, (), (override));
+  MOCK_METHOD(void, OnDelayedTaskComplete, (), (override));
 
  protected:
   ~AppServerTest() override = default;
diff --git a/chrome/updater/app/server/posix/app_server_posix.cc b/chrome/updater/app/server/posix/app_server_posix.cc
index 46d6994..106d98b6 100644
--- a/chrome/updater/app/server/posix/app_server_posix.cc
+++ b/chrome/updater/app/server/posix/app_server_posix.cc
@@ -19,28 +19,6 @@
 AppServerPosix::AppServerPosix() = default;
 AppServerPosix::~AppServerPosix() = default;
 
-void AppServerPosix::TaskStarted() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  ++tasks_running_;
-  VLOG(2) << "Starting task, " << tasks_running_ << " tasks running";
-}
-
-void AppServerPosix::TaskCompleted() {
-  main_task_runner_->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&AppServerPosix::AcknowledgeTaskCompletion, this),
-      external_constants()->ServerKeepAliveTime());
-}
-
-void AppServerPosix::AcknowledgeTaskCompletion() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (--tasks_running_ < 1) {
-    main_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&AppServerPosix::Shutdown, this, 0));
-  }
-  VLOG(2) << "Completing task, " << tasks_running_ << " tasks running";
-}
-
 void AppServerPosix::UninstallSelf() {
   UninstallCandidate(updater_scope());
 }
@@ -78,4 +56,10 @@
   return result == kErrorOk;
 }
 
+bool AppServerPosix::ShutdownIfIdleAfterTask() {
+  return true;
+}
+
+void AppServerPosix::OnDelayedTaskComplete() {}
+
 }  // namespace updater
diff --git a/chrome/updater/app/server/posix/app_server_posix.h b/chrome/updater/app/server/posix/app_server_posix.h
index bd348bb..37a178a 100644
--- a/chrome/updater/app/server/posix/app_server_posix.h
+++ b/chrome/updater/app/server/posix/app_server_posix.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/memory/scoped_refptr.h"
-#include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "chrome/updater/app/app_server.h"
@@ -29,11 +28,6 @@
   void ActiveDuty(scoped_refptr<UpdateService> update_service) override;
 
  private:
-  base::TimeDelta ServerKeepAlive();
-  void TaskStarted();
-  void TaskCompleted();
-  void AcknowledgeTaskCompletion();
-
   // Overrides of AppServer.
   void ActiveDutyInternal(
       scoped_refptr<UpdateServiceInternal> update_service_internal) override;
@@ -43,14 +37,14 @@
           register_callback) override;
   void UninstallSelf() override;
   void Uninitialize() override;
+  bool ShutdownIfIdleAfterTask() override;
+  void OnDelayedTaskComplete() override;
 
   std::unique_ptr<UpdateServiceInternalStub> active_duty_internal_stub_;
   std::unique_ptr<UpdateServiceStub> active_duty_stub_;
-  int tasks_running_ = 0;
   // Task runner bound to the main sequence and the update service instance.
   scoped_refptr<base::SequencedTaskRunner> main_task_runner_ =
       base::SequencedTaskRunner::GetCurrentDefault();
-  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 scoped_refptr<App> MakeAppServer();
diff --git a/chrome/updater/app/server/win/server.cc b/chrome/updater/app/server/win/server.cc
index 985e16b9..32bc4d3 100644
--- a/chrome/updater/app/server/win/server.cc
+++ b/chrome/updater/app/server/win/server.cc
@@ -314,16 +314,14 @@
       Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
           .IncrementObjectCount();
   VLOG(2) << "Starting task, Microsoft::WRL::Module count: " << count;
+  AppServer::TaskStarted();
 }
 
-void ComServerApp::TaskCompleted() {
-  main_task_runner_->PostDelayedTask(
-      FROM_HERE, base::BindOnce(&ComServerApp::AcknowledgeTaskCompletion, this),
-      external_constants()->ServerKeepAliveTime());
+bool ComServerApp::ShutdownIfIdleAfterTask() {
+  return false;
 }
 
-void ComServerApp::AcknowledgeTaskCompletion() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+void ComServerApp::OnDelayedTaskComplete() {
   const auto count =
       Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
           .DecrementObjectCount();
diff --git a/chrome/updater/app/server/win/server.h b/chrome/updater/app/server/win/server.h
index f5de598..b1fff6f 100644
--- a/chrome/updater/app/server/win/server.h
+++ b/chrome/updater/app/server/win/server.h
@@ -69,15 +69,6 @@
   // `UpdateServiceInternal` method. Increments the WRL Module count.
   void TaskStarted();
 
-  // Calls `AcknowledgeTaskCompletion` after a `ServerKeepAliveTime` delay.The
-  // delay allow for more COM calls to come into the server, reducing the
-  // overhead of the server process shutting down/coming back up.
-  void TaskCompleted();
-
-  // Called after each invocation of an `UpdateService` or
-  // `UpdateServiceInternal` method. Decrements the WRL Module count.
-  void AcknowledgeTaskCompletion();
-
   // Overrides for AppServer
   void ActiveDuty(scoped_refptr<UpdateService> update_service) override;
   void ActiveDutyInternal(
@@ -87,6 +78,10 @@
       base::RepeatingCallback<void(const RegistrationRequest&)>
           register_callback) override;
   void UninstallSelf() override;
+  bool ShutdownIfIdleAfterTask() override;
+
+  // Decrements the WRL Module count.
+  void OnDelayedTaskComplete() override;
 
   // Registers and unregisters the out-of-process COM class factories.
   HRESULT RegisterClassObjects();
@@ -113,8 +108,6 @@
   // |update_client| component.
   scoped_refptr<UpdateService> update_service_;
   scoped_refptr<UpdateServiceInternal> update_service_internal_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 // Returns a singleton application object bound to this COM server.
diff --git a/chrome/updater/constants.cc b/chrome/updater/constants.cc
index 288bcd1e..4ae0596b 100644
--- a/chrome/updater/constants.cc
+++ b/chrome/updater/constants.cc
@@ -102,6 +102,7 @@
 const char kDevOverrideKeyCrxVerifierFormat[] = "crx_verifier_format";
 const char kDevOverrideKeyGroupPolicies[] = "group_policies";
 const char kDevOverrideKeyOverinstallTimeout[] = "overinstall_timeout";
+const char kDevOverrideKeyIdleCheckPeriodSeconds[] = "idle_check_period";
 
 // Policy Management constants.
 const char kProxyModeDirect[] = "direct";
diff --git a/chrome/updater/constants.h b/chrome/updater/constants.h
index 7cc78dd..63b8f04e 100644
--- a/chrome/updater/constants.h
+++ b/chrome/updater/constants.h
@@ -225,6 +225,7 @@
 extern const char kDevOverrideKeyCrxVerifierFormat[];
 extern const char kDevOverrideKeyGroupPolicies[];
 extern const char kDevOverrideKeyOverinstallTimeout[];
+extern const char kDevOverrideKeyIdleCheckPeriodSeconds[];
 
 // Timing constants.
 // How long to wait for an application installer (such as chrome_installer.exe)
@@ -394,6 +395,9 @@
 // Failed to run install list.
 inline constexpr int kErrorFailedToRunInstallList = 44;
 
+// The server was running but had no tasks to do.
+inline constexpr int kErrorIdle = 45;
+
 inline constexpr int kErrorTagParsing = 50;
 
 // Metainstaller errors.
diff --git a/chrome/updater/external_constants.h b/chrome/updater/external_constants.h
index 5e433c7..ab66469c 100644
--- a/chrome/updater/external_constants.h
+++ b/chrome/updater/external_constants.h
@@ -60,6 +60,9 @@
   // Overrides the overinstall timeout.
   virtual base::TimeDelta OverinstallTimeout() const = 0;
 
+  // Overrides the idleness check period.
+  virtual base::TimeDelta IdleCheckPeriod() const = 0;
+
  protected:
   friend class base::RefCountedThreadSafe<ExternalConstants>;
   scoped_refptr<ExternalConstants> next_provider_;
diff --git a/chrome/updater/external_constants_builder.cc b/chrome/updater/external_constants_builder.cc
index 87e785c..22c56e4 100644
--- a/chrome/updater/external_constants_builder.cc
+++ b/chrome/updater/external_constants_builder.cc
@@ -150,6 +150,23 @@
   return *this;
 }
 
+ExternalConstantsBuilder& ExternalConstantsBuilder::ClearOverinstallTimeout() {
+  overrides_.Remove(kDevOverrideKeyOverinstallTimeout);
+  return *this;
+}
+
+ExternalConstantsBuilder& ExternalConstantsBuilder::SetIdleCheckPeriod(
+    const base::TimeDelta& idle_check_period) {
+  overrides_.Set(kDevOverrideKeyIdleCheckPeriodSeconds,
+                 static_cast<int>(idle_check_period.InSeconds()));
+  return *this;
+}
+
+ExternalConstantsBuilder& ExternalConstantsBuilder::ClearIdleCheckPeriod() {
+  overrides_.Remove(kDevOverrideKeyIdleCheckPeriodSeconds);
+  return *this;
+}
+
 bool ExternalConstantsBuilder::Overwrite() {
   const absl::optional<base::FilePath> override_path =
       GetOverrideFilePath(GetUpdaterScope());
@@ -195,6 +212,9 @@
     SetGroupPolicies(verifier->GroupPolicies());
   if (!overrides_.contains(kDevOverrideKeyOverinstallTimeout))
     SetOverinstallTimeout(verifier->OverinstallTimeout());
+  if (!overrides_.contains(kDevOverrideKeyIdleCheckPeriodSeconds)) {
+    SetIdleCheckPeriod(verifier->IdleCheckPeriod());
+  }
 
   return Overwrite();
 }
diff --git a/chrome/updater/external_constants_builder.h b/chrome/updater/external_constants_builder.h
index 261d2e83..9756f69 100644
--- a/chrome/updater/external_constants_builder.h
+++ b/chrome/updater/external_constants_builder.h
@@ -67,6 +67,11 @@
 
   ExternalConstantsBuilder& SetOverinstallTimeout(
       const base::TimeDelta& overinstall_timeout);
+  ExternalConstantsBuilder& ClearOverinstallTimeout();
+
+  ExternalConstantsBuilder& SetIdleCheckPeriod(
+      const base::TimeDelta& idle_check_period);
+  ExternalConstantsBuilder& ClearIdleCheckPeriod();
 
   // Write the external constants overrides file in the default location
   // with the values that have been previously set, replacing any file
diff --git a/chrome/updater/external_constants_builder_unittest.cc b/chrome/updater/external_constants_builder_unittest.cc
index 7abcf7c..f5369db6 100644
--- a/chrome/updater/external_constants_builder_unittest.cc
+++ b/chrome/updater/external_constants_builder_unittest.cc
@@ -72,7 +72,9 @@
       .SetUseCUP(false)
       .SetInitialDelay(base::Seconds(123))
       .SetServerKeepAliveTime(base::Seconds(2))
-      .SetGroupPolicies(group_policies);
+      .SetGroupPolicies(group_policies)
+      .SetOverinstallTimeout(base::Seconds(3))
+      .SetIdleCheckPeriod(base::Seconds(4));
   EXPECT_TRUE(builder.Overwrite());
 
   scoped_refptr<ExternalConstantsOverrider> verifier =
@@ -90,6 +92,8 @@
   EXPECT_EQ(verifier->InitialDelay(), base::Seconds(123));
   EXPECT_EQ(verifier->ServerKeepAliveTime(), base::Seconds(2));
   EXPECT_EQ(verifier->GroupPolicies().size(), 2U);
+  EXPECT_EQ(verifier->OverinstallTimeout(), base::Seconds(3));
+  EXPECT_EQ(verifier->IdleCheckPeriod(), base::Seconds(4));
 }
 
 TEST_F(ExternalConstantsBuilderTests, TestPartialOverrideWithMultipleURLs) {
@@ -134,6 +138,8 @@
                   .ClearInitialDelay()
                   .ClearServerKeepAliveSeconds()
                   .ClearGroupPolicies()
+                  .ClearOverinstallTimeout()
+                  .ClearIdleCheckPeriod()
                   .Overwrite());
 
   scoped_refptr<ExternalConstantsOverrider> verifier =
diff --git a/chrome/updater/external_constants_default.cc b/chrome/updater/external_constants_default.cc
index dc8f00b..f57e99b 100644
--- a/chrome/updater/external_constants_default.cc
+++ b/chrome/updater/external_constants_default.cc
@@ -51,6 +51,8 @@
     return base::Minutes(2);
   }
 
+  base::TimeDelta IdleCheckPeriod() const override { return base::Minutes(5); }
+
  private:
   ~DefaultExternalConstants() override = default;
 };
diff --git a/chrome/updater/external_constants_override.cc b/chrome/updater/external_constants_override.cc
index c8b4fb5..5655f47 100644
--- a/chrome/updater/external_constants_override.cc
+++ b/chrome/updater/external_constants_override.cc
@@ -196,6 +196,19 @@
   return base::Seconds(value->GetInt());
 }
 
+base::TimeDelta ExternalConstantsOverrider::IdleCheckPeriod() const {
+  if (!override_values_.contains(kDevOverrideKeyIdleCheckPeriodSeconds)) {
+    return next_provider_->IdleCheckPeriod();
+  }
+
+  const base::Value* value =
+      override_values_.Find(kDevOverrideKeyIdleCheckPeriodSeconds);
+  CHECK(value->is_int()) << "Unexpected type of override["
+                         << kDevOverrideKeyIdleCheckPeriodSeconds
+                         << "]: " << base::Value::GetTypeName(value->type());
+  return base::Seconds(value->GetInt());
+}
+
 // static
 scoped_refptr<ExternalConstantsOverrider>
 ExternalConstantsOverrider::FromDefaultJSONFile(
diff --git a/chrome/updater/external_constants_override.h b/chrome/updater/external_constants_override.h
index 5578fe4..c23c6e9 100644
--- a/chrome/updater/external_constants_override.h
+++ b/chrome/updater/external_constants_override.h
@@ -56,6 +56,7 @@
   crx_file::VerifierFormat CrxVerifierFormat() const override;
   base::Value::Dict GroupPolicies() const override;
   base::TimeDelta OverinstallTimeout() const override;
+  base::TimeDelta IdleCheckPeriod() const override;
 
  private:
   const base::Value::Dict override_values_;
diff --git a/chrome/updater/external_constants_override_unittest.cc b/chrome/updater/external_constants_override_unittest.cc
index 17538c8..f4a1e074 100644
--- a/chrome/updater/external_constants_override_unittest.cc
+++ b/chrome/updater/external_constants_override_unittest.cc
@@ -58,6 +58,8 @@
   overrides.Set(kDevOverrideKeyInitialDelay, 137.1);
   overrides.Set(kDevOverrideKeyServerKeepAliveSeconds, 1);
   overrides.Set(kDevOverrideKeyGroupPolicies, std::move(group_policies));
+  overrides.Set(kDevOverrideKeyOverinstallTimeout, 3);
+  overrides.Set(kDevOverrideKeyIdleCheckPeriodSeconds, 4);
   auto overrider = base::MakeRefCounted<ExternalConstantsOverrider>(
       std::move(overrides), CreateDefaultExternalConstants());
 
@@ -78,6 +80,8 @@
   EXPECT_EQ(overrider->InitialDelay(), base::Seconds(137.1));
   EXPECT_EQ(overrider->ServerKeepAliveTime(), base::Seconds(1));
   EXPECT_EQ(overrider->GroupPolicies().size(), 2U);
+  EXPECT_EQ(overrider->OverinstallTimeout(), base::Seconds(3));
+  EXPECT_EQ(overrider->IdleCheckPeriod(), base::Seconds(4));
 }
 
 TEST_F(ExternalConstantsOverriderTest, TestOverrideUnwrappedURL) {
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index 8a772ef..f84d5ea 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -84,6 +84,7 @@
   virtual void RunWakeAll() const = 0;
   virtual void RunWakeActive(int exit_code) const = 0;
   virtual void RunCrashMe() const = 0;
+  virtual void RunServer(int exit_code, bool internal) const = 0;
 
   virtual void CheckForUpdate(const std::string& app_id) const = 0;
   virtual void Update(const std::string& app_id,
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index ab5852c6..a870a8f 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -218,6 +218,12 @@
 
   void RunCrashMe() const override { RunCommand("run_crash_me", {}); }
 
+  void RunServer(int expected_exit_code, bool internal) const override {
+    RunCommand("run_server",
+               {Param("internal", internal ? "true" : "false"),
+                Param("exit_code", base::NumberToString(expected_exit_code))});
+  }
+
   void CheckForUpdate(const std::string& app_id) const override {
     RunCommand("check_for_update", {Param("app_id", app_id)});
   }
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 832bd25..039a60d 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -197,6 +197,10 @@
     updater::test::RunWakeActive(updater_scope_, exit_code);
   }
 
+  void RunServer(int exit_code, bool internal) const override {
+    updater::test::RunServer(updater_scope_, exit_code, internal);
+  }
+
   void CheckForUpdate(const std::string& app_id) const override {
     updater::test::CheckForUpdate(updater_scope_, app_id);
   }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 735824af..08555fe 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -309,6 +309,11 @@
     test_commands_->RunWakeActive(exit_code);
   }
 
+  void RunServer(int exit_code, bool internal) {
+    ASSERT_TRUE(WaitForUpdaterExit());
+    test_commands_->RunServer(exit_code, internal);
+  }
+
   void CheckForUpdate(const std::string& app_id) {
     test_commands_->CheckForUpdate(app_id);
   }
@@ -1025,6 +1030,19 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
+TEST_F(IntegrationTest, IdleServerExits) {
+#if BUILDFLAG(IS_WIN)
+  if (GetTestScope() == UpdaterScope::kSystem) {
+    GTEST_SKIP() << "System server startup is complicated on Windows.";
+  }
+#endif
+  ASSERT_NO_FATAL_FAILURE(Install());
+  ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
+  ASSERT_NO_FATAL_FAILURE(RunServer(kErrorIdle, true));
+  ASSERT_NO_FATAL_FAILURE(RunServer(kErrorIdle, false));
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
 TEST_F(IntegrationTest, SameVersionUpdate) {
   ScopedServer test_server(test_commands_);
   ASSERT_NO_FATAL_FAILURE(Install());
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index 46153f96..da97704c 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -317,6 +317,9 @@
     {"run_wake_active",
      WithSwitch("exit_code", WithSystemScope(Wrap(&RunWakeActive)))},
     {"run_crash_me", WithSystemScope(Wrap(&RunCrashMe))},
+    {"run_server",
+     WithSwitch("internal",
+                WithSwitch("exit_code", WithSystemScope(Wrap(&RunServer))))},
     {"update",
      WithSwitch("install_data_index",
                 (WithSwitch("app_id", WithSystemScope(Wrap(&Update)))))},
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index 05b4644..b60c59a 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -432,6 +432,22 @@
                        absl::nullopt);
 }
 
+void RunServer(UpdaterScope scope, int expected_exit_code, bool internal) {
+  const absl::optional<base::FilePath> installed_executable_path =
+      GetVersionedInstallDirectory(scope, base::Version(kUpdaterVersion))
+          ->Append(GetExecutableRelativePath());
+  ASSERT_TRUE(installed_executable_path);
+  ASSERT_TRUE(base::PathExists(*installed_executable_path));
+  base::CommandLine command_line(*installed_executable_path);
+  command_line.AppendSwitch(kServerSwitch);
+  command_line.AppendSwitchASCII(
+      kServerServiceSwitch, internal ? kServerUpdateServiceInternalSwitchValue
+                                     : kServerUpdateServiceSwitchValue);
+  int exit_code = -1;
+  Run(scope, command_line, &exit_code);
+  ASSERT_EQ(exit_code, expected_exit_code);
+}
+
 void CheckForUpdate(UpdaterScope scope, const std::string& app_id) {
   scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
   base::RunLoop loop;
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index de4ac77..5001341 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -116,6 +116,10 @@
 // Starts an updater process with switch `--crash-me`.
 void RunCrashMe(UpdaterScope scope);
 
+// Runs the server and waits for it to exit. Assert that it exits with
+// `exit_code`.
+void RunServer(UpdaterScope scope, int exit_code, bool internal);
+
 // Invokes the active instance's UpdateService::Update (via RPC) for an app.
 void Update(UpdaterScope scope,
             const std::string& app_id,
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index 24d561a..80289e7 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -150,6 +150,7 @@
                   .SetServerKeepAliveTime(base::Seconds(1))
                   .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .SetOverinstallTimeout(TestTimeouts::action_timeout())
+                  .SetIdleCheckPeriod(base::Seconds(4))
                   .Modify());
 }
 
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index e52c7a1..2a0153d 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -88,6 +88,7 @@
                   .SetServerKeepAliveTime(base::Seconds(1))
                   .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .SetOverinstallTimeout(base::Seconds(5))
+                  .SetIdleCheckPeriod(base::Seconds(4))
                   .Modify());
 }
 
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 5af4d13f..598d45a 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -628,6 +628,7 @@
                   .SetServerKeepAliveTime(base::Seconds(1))
                   .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .SetOverinstallTimeout(base::Seconds(11))
+                  .SetIdleCheckPeriod(base::Seconds(4))
                   .Modify());
 }
 
diff --git a/docs/updater/functional_spec.md b/docs/updater/functional_spec.md
index fc02fa41..8267e4d 100644
--- a/docs/updater/functional_spec.md
+++ b/docs/updater/functional_spec.md
@@ -885,6 +885,18 @@
 the logon trigger on the scheduled task, as well as the "Run" registry entry in
 `HKCU` for redundancy.
 
+### Server Lifetime
+The updater's RPC server starts and waits for incoming RPCs. The server
+considers itself idle if it has not been processing any RPC in the last ten
+seconds. Every five minutes, the updater will check itself for idleness and
+shut down if idle.
+
+Additionally, on macOS, after answering at least one RPC, the server will shut
+itself down as soon as it becomes idle.
+
+Additionally, on Windows, the updater will shut itself down if all clients
+release their references to the server.
+
 ### On-Demand Updates
 The updater exposes an RPC interface for any user to trigger an update check.
 The update can be triggered by any user on the system, even in the system scope.