Nice automatic updates up to a point, then renice if necessary.

BUG=5488
TEST=unit tests, gmerged on device and completed an update
successfully with two 100% CPU "nice -n -20" processes
running in the background.

Review URL: http://codereview.chromium.org/3053049
diff --git a/update_attempter.cc b/update_attempter.cc
index 50c59ab..a099d7c 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -114,6 +114,28 @@
   return code;
 }
 
+UpdateAttempter::UpdateAttempter(PrefsInterface* prefs,
+                                 MetricsLibraryInterface* metrics_lib)
+    : dbus_service_(NULL),
+      prefs_(prefs),
+      metrics_lib_(metrics_lib),
+      priority_(utils::kProcessPriorityNormal),
+      manage_priority_source_(NULL),
+      status_(UPDATE_STATUS_IDLE),
+      download_progress_(0.0),
+      last_checked_time_(0),
+      new_version_("0.0.0.0"),
+      new_size_(0) {
+  last_notify_time_.tv_sec = 0;
+  last_notify_time_.tv_nsec = 0;
+  if (utils::FileExists(kUpdateCompletedMarker))
+    status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
+}
+
+UpdateAttempter::~UpdateAttempter() {
+  CleanupPriorityManagement();
+}
+
 void UpdateAttempter::Update(const std::string& app_version,
                              const std::string& omaha_url) {
   if (status_ == UPDATE_STATUS_UPDATED_NEED_REBOOT) {
@@ -242,6 +264,9 @@
   LOG(INFO) << "Processing Done.";
   actions_.clear();
 
+  // Reset process priority back to normal.
+  CleanupPriorityManagement();
+
   if (status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) {
     LOG(INFO) << "Error event sent.";
     SetStatusAndNotify(UPDATE_STATUS_IDLE);
@@ -269,6 +294,8 @@
 }
 
 void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+  // Reset process priority back to normal.
+  CleanupPriorityManagement();
   download_progress_ = 0.0;
   SetStatusAndNotify(UPDATE_STATUS_IDLE);
   actions_.clear();
@@ -301,6 +328,7 @@
     // TODO(adlr): put version in InstallPlan
     new_version_ = "0.0.0.0";
     new_size_ = plan.size;
+    SetupPriorityManagement();
   } else if (type == DownloadAction::StaticType()) {
     SetStatusAndNotify(UPDATE_STATUS_FINALIZING);
   }
@@ -404,4 +432,54 @@
   return true;
 }
 
+void UpdateAttempter::SetPriority(utils::ProcessPriority priority) {
+  if (priority_ == priority) {
+    return;
+  }
+  if (utils::SetProcessPriority(priority)) {
+    priority_ = priority;
+    LOG(INFO) << "Process priority = " << priority_;
+  }
+}
+
+void UpdateAttempter::SetupPriorityManagement() {
+  if (manage_priority_source_) {
+    LOG(ERROR) << "Process priority timeout source hasn't been destroyed.";
+    CleanupPriorityManagement();
+  }
+  const int kPriorityTimeout = 10 * 60;  // 10 minutes
+  manage_priority_source_ = g_timeout_source_new_seconds(kPriorityTimeout);
+  g_source_set_callback(manage_priority_source_,
+                        StaticManagePriorityCallback,
+                        this,
+                        NULL);
+  g_source_attach(manage_priority_source_, NULL);
+  SetPriority(utils::kProcessPriorityLow);
+}
+
+void UpdateAttempter::CleanupPriorityManagement() {
+  if (manage_priority_source_) {
+    g_source_destroy(manage_priority_source_);
+    manage_priority_source_ = NULL;
+  }
+  SetPriority(utils::kProcessPriorityNormal);
+}
+
+gboolean UpdateAttempter::StaticManagePriorityCallback(gpointer data) {
+  return reinterpret_cast<UpdateAttempter*>(data)->ManagePriorityCallback();
+}
+
+bool UpdateAttempter::ManagePriorityCallback() {
+  // If the current process priority is below normal, set it to normal
+  // and let GLib invoke this callback again.
+  if (utils::ComparePriorities(priority_, utils::kProcessPriorityNormal) < 0) {
+    SetPriority(utils::kProcessPriorityNormal);
+    return true;
+  }
+  // Set the priority to high and let GLib destroy the timeout source.
+  SetPriority(utils::kProcessPriorityHigh);
+  manage_priority_source_ = NULL;
+  return false;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index 6fd4c16..1993321 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -6,10 +6,13 @@
 #define CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_ATTEMPTER_H__
 
 #include <time.h>
+
 #include <tr1/memory>
 #include <string>
 #include <vector>
+
 #include <glib.h>
+
 #include "update_engine/action_processor.h"
 #include "update_engine/download_action.h"
 #include "update_engine/omaha_request_params.h"
@@ -20,6 +23,10 @@
 
 namespace chromeos_update_engine {
 
+namespace utils {
+enum ProcessPriority;
+};
+
 extern const char* kUpdateCompletedMarker;
 
 enum UpdateStatus {
@@ -38,20 +45,9 @@
 class UpdateAttempter : public ActionProcessorDelegate,
                         public DownloadActionDelegate {
  public:
-  UpdateAttempter(PrefsInterface* prefs, MetricsLibraryInterface* metrics_lib)
-      : dbus_service_(NULL),
-        prefs_(prefs),
-        metrics_lib_(metrics_lib),
-        status_(UPDATE_STATUS_IDLE),
-        download_progress_(0.0),
-        last_checked_time_(0),
-        new_version_("0.0.0.0"),
-        new_size_(0) {
-    last_notify_time_.tv_sec = 0;
-    last_notify_time_.tv_nsec = 0;
-    if (utils::FileExists(kUpdateCompletedMarker))
-      status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
-  }
+  UpdateAttempter(PrefsInterface* prefs, MetricsLibraryInterface* metrics_lib);
+  ~UpdateAttempter();
+
   // Checks for update and, if a newer version is available, attempts
   // to update the system. Non-empty |in_app_version| or
   // |in_update_url| prevents automatic detection of the parameter.
@@ -109,6 +105,26 @@
   // returns true. Returns false otherwise.
   bool ScheduleErrorEventAction();
 
+  // Sets the process priority to |priority| and updates |priority_|
+  // if the new |priority| is different than the current |priority_|,
+  // otherwise simply returns.
+  void SetPriority(utils::ProcessPriority priority);
+
+  // Set the process priority to low and sets up timeout events to
+  // increase the priority gradually to high.
+  void SetupPriorityManagement();
+
+  // Resets the process priority to normal and destroys any scheduled
+  // timeout sources.
+  void CleanupPriorityManagement();
+
+  // The process priority timeout source callback increases the
+  // current priority by one step (low goes to normal, normal goes to
+  // high). Returns true if the callback must be invoked again after a
+  // timeout, or false if GLib can destroy this timeout source.
+  static gboolean StaticManagePriorityCallback(gpointer data);
+  bool ManagePriorityCallback();
+
   struct timespec last_notify_time_;
 
   std::vector<std::tr1::shared_ptr<AbstractAction> > actions_;
@@ -130,6 +146,12 @@
   // Pending error event, if any.
   scoped_ptr<OmahaEvent> error_event_;
 
+  // Current process priority.
+  utils::ProcessPriority priority_;
+
+  // The process priority management timeout source.
+  GSource* manage_priority_source_;
+
   // For status:
   UpdateStatus status_;
   double download_progress_;
diff --git a/utils.cc b/utils.cc
index 05acc15..134ad80 100644
--- a/utils.cc
+++ b/utils.cc
@@ -5,6 +5,7 @@
 #include "update_engine/utils.h"
 
 #include <sys/mount.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <dirent.h>
@@ -444,6 +445,18 @@
   return true;
 }
 
+bool SetProcessPriority(ProcessPriority priority) {
+  int prio = static_cast<int>(priority);
+  LOG(INFO) << "Setting process priority to " << prio;
+  TEST_AND_RETURN_FALSE(setpriority(PRIO_PROCESS, 0, prio) == 0);
+  return true;
+}
+
+int ComparePriorities(ProcessPriority priority_lhs,
+                      ProcessPriority priority_rhs) {
+  return static_cast<int>(priority_rhs) - static_cast<int>(priority_lhs);
+}
+
 const char* const kStatefulPartition = "/mnt/stateful_partition";
 
 }  // namespace utils
diff --git a/utils.h b/utils.h
index 6ce4cf3..8438313 100644
--- a/utils.h
+++ b/utils.h
@@ -6,11 +6,14 @@
 #define CHROMEOS_PLATFORM_UPDATE_ENGINE_UTILS_H__
 
 #include <errno.h>
+
 #include <algorithm>
 #include <set>
 #include <string>
 #include <vector>
+
 #include <glib.h>
+
 #include "update_engine/action.h"
 #include "update_engine/action_processor.h"
 
@@ -183,6 +186,21 @@
 // Returns empty string on failure.
 const std::string BootKernelDevice(const std::string& boot_device);
 
+enum ProcessPriority {
+  kProcessPriorityHigh = -10,
+  kProcessPriorityNormal = 0,
+  kProcessPriorityLow = 10,
+};
+
+// Compares process priorities and returns an integer that is less
+// than, equal to or greater than 0 if |priority_lhs| is,
+// respectively, lower than, same as or higher than |priority_rhs|.
+int ComparePriorities(ProcessPriority priority_lhs,
+                      ProcessPriority priority_rhs);
+
+// Sets the current process priority to |priority|. Returns true on
+// success, false otherwise.
+bool SetProcessPriority(ProcessPriority priority);
 
 }  // namespace utils
 
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 575d91f..70eb6c7 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -157,4 +157,15 @@
   EXPECT_EQ("3", utils::PartitionNumber("/dev/mmc0p3"));
 }
 
+TEST(UtilsTest, ComparePriorities) {
+  EXPECT_LT(utils::ComparePriorities(utils::kProcessPriorityLow,
+                                     utils::kProcessPriorityNormal), 0);
+  EXPECT_GT(utils::ComparePriorities(utils::kProcessPriorityNormal,
+                                     utils::kProcessPriorityLow), 0);
+  EXPECT_EQ(utils::ComparePriorities(utils::kProcessPriorityNormal,
+                                     utils::kProcessPriorityNormal), 0);
+  EXPECT_GT(utils::ComparePriorities(utils::kProcessPriorityHigh,
+                                     utils::kProcessPriorityNormal), 0);
+}
+
 }  // namespace chromeos_update_engine