Bug 980027 - Part 1: Provide mechanism to set thread priorities. r=gsvelto, r=dhylands
authorMason Chang <mchang@mozilla.com>
Mon, 05 May 2014 11:37:00 -0400
changeset 181603 5964662eaece1c63ed2186e9af9d5eceb25d9a62
parent 181602 1c40e473da4c6cf3b0231c34375776d76ea393d6
child 181604 9083afeee588c2993b30a387533f2772eadf0eb2
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersgsvelto, dhylands
bugs980027
milestone32.0a1
Bug 980027 - Part 1: Provide mechanism to set thread priorities. r=gsvelto, r=dhylands
hal/Hal.cpp
hal/Hal.h
hal/HalTypes.h
hal/fallback/FallbackThreadPriority.cpp
hal/gonk/GonkHal.cpp
hal/moz.build
hal/sandbox/SandboxHal.cpp
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -866,16 +866,22 @@ SetProcessPriority(int aPid,
 {
   // n.b. The sandboxed implementation crashes; SetProcessPriority works only
   // from the main process.
   MOZ_ASSERT(aBackgroundLRU == 0 || aPriority == PROCESS_PRIORITY_BACKGROUND);
   PROXY_IF_SANDBOXED(SetProcessPriority(aPid, aPriority, aCPUPriority,
                                         aBackgroundLRU));
 }
 
+void
+SetCurrentThreadPriority(hal::ThreadPriority aThreadPriority)
+{
+  PROXY_IF_SANDBOXED(SetCurrentThreadPriority(aThreadPriority));
+}
+
 // From HalTypes.h.
 const char*
 ProcessPriorityToString(ProcessPriority aPriority)
 {
   switch (aPriority) {
   case PROCESS_PRIORITY_MASTER:
     return "MASTER";
   case PROCESS_PRIORITY_PREALLOC:
@@ -895,16 +901,28 @@ ProcessPriorityToString(ProcessPriority 
   case PROCESS_PRIORITY_UNKNOWN:
     return "UNKNOWN";
   default:
     MOZ_ASSERT(false);
     return "???";
   }
 }
 
+const char *
+ThreadPriorityToString(ThreadPriority aPriority)
+{
+  switch (aPriority) {
+    case THREAD_PRIORITY_COMPOSITOR:
+      return "COMPOSITOR";
+    default:
+      MOZ_ASSERT(false);
+      return "???";
+  }
+}
+
 // From HalTypes.h.
 const char*
 ProcessPriorityToString(ProcessPriority aPriority,
                         ProcessCPUPriority aCPUPriority)
 {
   // Sorry this is ugly.  At least it's all in one place.
   //
   // We intentionally fall through if aCPUPriority is invalid; we won't hit any
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -491,16 +491,24 @@ bool SetAlarm(int32_t aSeconds, int32_t 
  * background processes higher nice values.  On other platforms, we might
  * ignore this call entirely.
  */
 void SetProcessPriority(int aPid,
                         hal::ProcessPriority aPriority,
                         hal::ProcessCPUPriority aCPUPriority,
                         uint32_t aLRU = 0);
 
+
+/**
+ * Set the current thread's priority to appropriate platform-specific value for
+ * given functionality. Instead of providing arbitrary priority numbers you
+ * must specify a type of function like THREAD_PRIORITY_COMPOSITOR.
+ */
+void SetCurrentThreadPriority(hal::ThreadPriority aThreadPriority);
+
 /**
  * Register an observer for the FM radio.
  */
 void RegisterFMRadioObserver(hal::FMRadioObserver* aRadioObserver);
 
 /**
  * Unregister the observer for the FM radio.
  */
--- a/hal/HalTypes.h
+++ b/hal/HalTypes.h
@@ -97,30 +97,54 @@ enum ProcessPriority {
 };
 
 enum ProcessCPUPriority {
   PROCESS_CPU_PRIORITY_LOW,
   PROCESS_CPU_PRIORITY_NORMAL,
   NUM_PROCESS_CPU_PRIORITY
 };
 
-// Convert a ProcessPriority enum value (with an optional ProcessCPUPriority)
-// to a string.  The strings returned by this function are statically
-// allocated; do not attempt to free one!
-//
-// If you pass an unknown process priority (or NUM_PROCESS_PRIORITY), we
-// fatally assert in debug builds and otherwise return "???".
+/**
+ * Values that can be passed to hal::SetCurrentThreadPriority().  These should be
+ * functional in nature, such as COMPOSITOR, instead of levels, like LOW/HIGH.
+ * This allows us to tune our priority scheme for the system in one place such
+ * that it makes sense holistically for the overall operating system.  On gonk
+ * or android we may want different priority schemes than on windows, etc.
+ */
+enum ThreadPriority {
+  THREAD_PRIORITY_COMPOSITOR,
+  NUM_THREAD_PRIORITY
+};
+
+/**
+ * Convert a ProcessPriority enum value (with an optional ProcessCPUPriority)
+ * to a string.  The strings returned by this function are statically
+ * allocated; do not attempt to free one!
+ *
+ * If you pass an unknown process priority, we fatally assert in debug
+ * builds and otherwise return "???".
+ */
 const char*
 ProcessPriorityToString(ProcessPriority aPriority);
 
 const char*
 ProcessPriorityToString(ProcessPriority aPriority,
                         ProcessCPUPriority aCPUPriority);
 
 /**
+ * Convert a ThreadPriority enum value to a string.  The strings returned by
+ * this function are statically allocated; do not attempt to free one!
+ *
+ * If you pass an unknown process priority, we assert in debug builds
+ * and otherwise return "???".
+ */
+const char *
+ThreadPriorityToString(ThreadPriority aPriority);
+
+/**
  * Used by ModifyWakeLock
  */
 enum WakeLockControl {
   WAKE_LOCK_REMOVE_ONE = -1,
   WAKE_LOCK_NO_CHANGE  = 0,
   WAKE_LOCK_ADD_ONE    = 1,
   NUM_WAKE_LOCK
 };
new file mode 100644
--- /dev/null
+++ b/hal/fallback/FallbackThreadPriority.cpp
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Hal.h"
+
+using namespace mozilla::hal;
+
+namespace mozilla {
+namespace hal_impl {
+
+void
+SetCurrentThreadPriority(ThreadPriority aPriority)
+{
+  HAL_LOG(("FallbackThreadPriority - SetCurrentThreadPriority(%d)\n",
+           ThreadPriorityToString(aPriority)));
+}
+
+} // hal_impl
+} // namespace mozilla
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -16,16 +16,17 @@
  */
 
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/android_alarm.h>
 #include <math.h>
 #include <regex.h>
+#include <sched.h>
 #include <stdio.h>
 #include <sys/klog.h>
 #include <sys/syscall.h>
 #include <sys/resource.h>
 #include <time.h>
 #include <asm/page.h>
 
 #include "mozilla/DebugOnly.h"
@@ -33,16 +34,17 @@
 #include "android/log.h"
 #include "cutils/properties.h"
 #include "hardware/hardware.h"
 #include "hardware/lights.h"
 #include "hardware_legacy/uevent.h"
 #include "hardware_legacy/vibrator.h"
 #include "hardware_legacy/power.h"
 #include "libdisplay/GonkDisplay.h"
+#include "utils/threads.h"
 
 #include "base/message_loop.h"
 
 #include "Hal.h"
 #include "HalImpl.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/battery/Constants.h"
 #include "mozilla/FileUtils.h"
@@ -1352,28 +1354,45 @@ SetNiceForPid(int aPid, int aNice)
       // (The |tidlong == aPid| check is very important; without it, we'll
       // renice aPid twice, and the second renice will be relative to the
       // priority set by the first renice.)
       continue;
     }
 
     int tid = static_cast<int>(tidlong);
 
+    // Do not set the priority of threads running with a real-time policy
+    // as part of the bulk process adjustment.  These threads need to run
+    // at their specified priority in order to meet timing guarantees.
+    int schedPolicy = sched_getscheduler(tid);
+    if (schedPolicy == SCHED_FIFO || schedPolicy == SCHED_RR) {
+      continue;
+    }
+
     errno = 0;
     // Get and set the task's new priority.
     int origtaskpriority = getpriority(PRIO_PROCESS, tid);
     if (errno) {
       LOG("Unable to get nice for tid=%d (pid=%d); error %d.  This isn't "
           "necessarily a problem; it could be a benign race condition.",
           tid, aPid, errno);
       continue;
     }
 
     int newtaskpriority =
       std::max(origtaskpriority - origProcPriority + aNice, aNice);
+
+    // Do not reduce priority of threads already running at priorities greater
+    // than normal.  These threads are likely special service threads that need
+    // elevated priorities to process audio, display composition, etc.
+    if (newtaskpriority > origtaskpriority &&
+        origtaskpriority < ANDROID_PRIORITY_NORMAL) {
+      continue;
+    }
+
     rv = setpriority(PRIO_PROCESS, tid, newtaskpriority);
 
     if (rv) {
       LOG("Unable to set nice for tid=%d (pid=%d); error %d.  This isn't "
           "necessarily a problem; it could be a benign race condition.",
           tid, aPid, errno);
       continue;
     }
@@ -1458,16 +1477,144 @@ SetProcessPriority(int aPid,
   }
 
   if (NS_SUCCEEDED(rv)) {
     LOG("Setting nice for pid %d to %d", aPid, nice);
     SetNiceForPid(aPid, nice);
   }
 }
 
+static bool
+IsValidRealTimePriority(int aValue, int aSchedulePolicy)
+{
+  return (aValue >= sched_get_priority_min(aSchedulePolicy)) &&
+         (aValue <= sched_get_priority_max(aSchedulePolicy));
+}
+
+static void
+SetThreadNiceValue(pid_t aTid, ThreadPriority aThreadPriority, int aValue)
+{
+  MOZ_ASSERT(aThreadPriority < NUM_THREAD_PRIORITY);
+  MOZ_ASSERT(aThreadPriority >= 0);
+
+  LOG("Setting thread %d to priority level %s; nice level %d",
+      aTid, ThreadPriorityToString(aThreadPriority), aValue);
+  int rv = setpriority(PRIO_PROCESS, aTid, aValue);
+
+  if (rv) {
+    LOG("Failed to set thread %d to priority level %s; error %s",
+        aTid, ThreadPriorityToString(aThreadPriority), strerror(errno));
+  }
+}
+
+static void
+SetRealTimeThreadPriority(pid_t aTid,
+                          ThreadPriority aThreadPriority,
+                          int aValue)
+{
+  int policy = SCHED_FIFO;
+
+  MOZ_ASSERT(aThreadPriority < NUM_THREAD_PRIORITY);
+  MOZ_ASSERT(aThreadPriority >= 0);
+  MOZ_ASSERT(IsValidRealTimePriority(aValue, policy), "Invalid real time priority");
+
+  // Setting real time priorities requires using sched_setscheduler
+  LOG("Setting thread %d to priority level %s; Real Time priority %d, Schedule FIFO",
+      aTid, ThreadPriorityToString(aThreadPriority), aValue);
+  sched_param schedParam;
+  schedParam.sched_priority = aValue;
+  int rv = sched_setscheduler(aTid, policy, &schedParam);
+
+  if (rv) {
+    LOG("Failed to set thread %d to real time priority level %s; error %s",
+        aTid, ThreadPriorityToString(aThreadPriority), strerror(errno));
+  }
+}
+
+static void
+SetThreadPriority(pid_t aTid, hal::ThreadPriority aThreadPriority)
+{
+  // See bug 999115, we can only read preferences on the main thread otherwise
+  // we create a race condition in HAL
+  MOZ_ASSERT(NS_IsMainThread(), "Can only set thread priorities on main thread");
+  MOZ_ASSERT(aThreadPriority >= 0);
+
+  const char* threadPriorityStr;
+  switch (aThreadPriority) {
+    case THREAD_PRIORITY_COMPOSITOR:
+      threadPriorityStr = ThreadPriorityToString(aThreadPriority);
+      break;
+    default:
+      LOG("Unrecognized thread priority %d; Doing nothing", aThreadPriority);
+      return;
+  }
+
+  int realTimePriority = Preferences::GetInt(
+    nsPrintfCString("hal.gonk.%s.rt_priority", threadPriorityStr).get());
+
+  if (IsValidRealTimePriority(realTimePriority, SCHED_FIFO)) {
+    SetRealTimeThreadPriority(aTid, aThreadPriority, realTimePriority);
+    return;
+  }
+
+  int niceValue = Preferences::GetInt(
+    nsPrintfCString("hal.gonk.%s.nice", threadPriorityStr).get());
+
+  SetThreadNiceValue(aTid, aThreadPriority, niceValue);
+}
+
+namespace {
+
+/**
+ * This class sets the priority of threads given the kernel thread's id and a
+ * value taken from hal::ThreadPriority.
+ *
+ * This runnable must always be dispatched to the main thread otherwise it will fail.
+ * We have to run this from the main thread since preferences can only be read on
+ * main thread.
+ */
+class SetThreadPriorityRunnable : public nsRunnable
+{
+public:
+  SetThreadPriorityRunnable(pid_t aThreadId, hal::ThreadPriority aThreadPriority)
+    : mThreadId(aThreadId)
+    , mThreadPriority(aThreadPriority)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Can only set thread priorities on main thread");
+    hal_impl::SetThreadPriority(mThreadId, mThreadPriority);
+    return NS_OK;
+  }
+
+private:
+  pid_t mThreadId;
+  hal::ThreadPriority mThreadPriority;
+};
+
+} // anonymous namespace
+
+void
+SetCurrentThreadPriority(ThreadPriority aThreadPriority)
+{
+  switch (aThreadPriority) {
+    case THREAD_PRIORITY_COMPOSITOR: {
+      pid_t threadId = gettid();
+      nsCOMPtr<nsIRunnable> runnable =
+        new SetThreadPriorityRunnable(threadId, aThreadPriority);
+      NS_DispatchToMainThread(runnable);
+      break;
+    }
+    default:
+      LOG("Unrecognized thread priority %d; Doing nothing", aThreadPriority);
+      return;
+  }
+}
+
 void
 FactoryReset()
 {
   nsCOMPtr<nsIRecoveryService> recoveryService =
     do_GetService("@mozilla.org/recovery-service;1");
   if (!recoveryService) {
     NS_WARNING("Could not get recovery service!");
     return;
--- a/hal/moz.build
+++ b/hal/moz.build
@@ -150,16 +150,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk
     UNIFIED_SOURCES += [
         'fallback/FallbackDiskSpaceWatcher.cpp',
         'fallback/FallbackFactoryReset.cpp',
         'fallback/FallbackFMRadio.cpp',
         'fallback/FallbackLights.cpp',
         'fallback/FallbackProcessPriority.cpp',
         'fallback/FallbackScreenPower.cpp',
         'fallback/FallbackSwitch.cpp',
+        'fallback/FallbackThreadPriority.cpp',
         'fallback/FallbackTime.cpp',
         'fallback/FallbackWakeLocks.cpp',
     ]
 
 # Fallbacks for backends implemented on Android only.
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     UNIFIED_SOURCES += [
         'fallback/FallbackNetwork.cpp',
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -358,16 +358,22 @@ SetProcessPriority(int aPid,
                    ProcessPriority aPriority,
                    ProcessCPUPriority aCPUPriority,
                    uint32_t aBackgroundLRU)
 {
   NS_RUNTIMEABORT("Only the main process may set processes' priorities.");
 }
 
 void
+SetCurrentThreadPriority(ThreadPriority aThreadPriority)
+{
+  NS_RUNTIMEABORT("Setting thread priority cannot be called from sandboxed contexts.");
+}
+
+void
 EnableFMRadio(const hal::FMRadioSettings& aSettings)
 {
   NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
 }
 
 void
 DisableFMRadio()
 {