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 200825 5964662eaece1c63ed2186e9af9d5eceb25d9a62
parent 200824 1c40e473da4c6cf3b0231c34375776d76ea393d6
child 200826 9083afeee588c2993b30a387533f2772eadf0eb2
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgsvelto, dhylands
bugs980027
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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()
 {