Bug 841651: Implement PR_SetThreadPriority() on Linux-based platforms
authorGabriele Svelto <gsvelto@mozilla.com>
Thu, 14 Mar 2013 10:25:13 -0700
changeset 4449 39cb7968bf9b302f60f813228d4752ef9c4acd12
parent 4448 0e2e5e09fbac778e52354b9bdf73a6b4678f934c
child 4450 b116af2df022d8404fa082bb3e06de418d2ac1c8
push id3
push userwtc@google.com
push dateThu, 14 Mar 2013 17:27:21 +0000
bugs841651
Bug 841651: Implement PR_SetThreadPriority() on Linux-based platforms using per-thread nice values. r=wtc.
configure
configure.in
pr/include/private/primpl.h
pr/src/pthreads/ptthread.c
--- a/configure
+++ b/configure
@@ -5616,17 +5616,17 @@ fi
 echo "$ac_t""$ac_cv_prog_gcc_traditional" 1>&6
   if test $ac_cv_prog_gcc_traditional = yes; then
     CC="$CC -traditional"
   fi
 fi
 
 _SAVE_LIBS="$LIBS"
 LIBS="$LIBS $OS_LIBS"
-for ac_func in lchown strerror dladdr
+for ac_func in dladdr gettid lchown setpriority strerror syscall
 do
 echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
 echo "configure:5628: checking for $ac_func" >&5
 if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
 #line 5633 "configure"
--- a/configure.in
+++ b/configure.in
@@ -2583,17 +2583,17 @@ dnl AC_HEADER_TIME
 dnl AC_STRUCT_TM
 
 dnl ========================================================
 dnl Checks for library functions.
 dnl ========================================================
 AC_PROG_GCC_TRADITIONAL
 _SAVE_LIBS="$LIBS"
 LIBS="$LIBS $OS_LIBS"
-AC_CHECK_FUNCS(lchown strerror dladdr)
+AC_CHECK_FUNCS(dladdr gettid lchown setpriority strerror syscall)
 LIBS="$_SAVE_LIBS"
 
 dnl AC_FUNC_MEMCMP
 dnl AC_FUNC_MMAP
 dnl AC_FUNC_SETVBUF_REVERSED
 dnl AC_FUNC_STRCOLL
 dnl AC_FUNC_STRFTIME
 dnl AC_FUNC_UTIME_NULL
--- a/pr/include/private/primpl.h
+++ b/pr/include/private/primpl.h
@@ -45,16 +45,20 @@ typedef struct PRSegment PRSegment;
 #include "obsolete/probslet.h"
 
 #ifdef _PR_HAVE_POSIX_SEMAPHORES
 #include <semaphore.h>
 #elif defined(_PR_HAVE_SYSV_SEMAPHORES)
 #include <sys/sem.h>
 #endif
 
+#ifdef HAVE_SYSCALL
+#include <sys/syscall.h>
+#endif
+
 /*************************************************************************
 *****  A Word about Model Dependent Function Naming Convention ***********
 *************************************************************************/
 
 /*
 NSPR 2.0 must implement its function across a range of platforms 
 including: MAC, Windows/16, Windows/95, Windows/NT, and several
 variants of Unix. Each implementation shares common code as well 
@@ -181,16 +185,27 @@ typedef struct PTDebug
     PRUintn cvars_created, cvars_destroyed;
     PRUintn cvars_notified, delayed_cv_deletes;
 } PTDebug;
 
 #endif /* defined(DEBUG) */
 
 NSPR_API(void) PT_FPrintStats(PRFileDesc *fd, const char *msg);
 
+/*
+ * On Linux and its derivatives POSIX priority scheduling works only for
+ * real-time threads. On those platforms we set thread's nice values
+ * instead which requires us to track kernel thread IDs for each POSIX
+ * thread we create.
+ */
+#if defined(LINUX) && defined(HAVE_SETPRIORITY) && \
+    ((defined(HAVE_SYSCALL) && defined(SYS_gettid)) || defined(HAVE_GETTID))
+#define _PR_NICE_PRIORITY_SCHEDULING
+#endif
+
 #else /* defined(_PR_PTHREADS) */
 
 NSPR_API(void) PT_FPrintStats(PRFileDesc *fd, const char *msg);
 
 /*
 ** This section is contains those parts needed to implement NSPR on
 ** platforms in general. One would assume that the pthreads implementation
 ** included lots of the same types, at least conceptually.
@@ -1535,16 +1550,19 @@ struct PRThread {
     PRInt32 osErrorCode;            /* mapping of errorCode | zero */
     PRIntn  errorStringLength;      /* textLength from last call to PR_SetErrorText() */
     PRInt32 errorStringSize;        /* malloc()'d size of buffer | zero */
     char *errorString;              /* current error string | NULL */
     char *name;                     /* thread's name */
 
 #if defined(_PR_PTHREADS)
     pthread_t id;                   /* pthread identifier for the thread */
+#ifdef _PR_NICE_PRIORITY_SCHEDULING
+    pid_t tid;                      /* Linux-specific kernel thread ID */
+#endif
     PRBool okToDelete;              /* ok to delete the PRThread struct? */
     PRCondVar *waiting;             /* where the thread is waiting | NULL */
     void *sp;                       /* recorded sp for garbage collection */
     PRThread *next, *prev;          /* simple linked list of all threads */
     PRUint32 suspend;               /* used to store suspend and resume flags */
 #ifdef PT_NO_SIGTIMEDWAIT
     pthread_mutex_t suspendResumeMutex;
     pthread_cond_t suspendResumeCV;
--- a/pr/src/pthreads/ptthread.c
+++ b/pr/src/pthreads/ptthread.c
@@ -23,16 +23,24 @@
 
 #ifdef SYMBIAN
 /* In Open C sched_get_priority_min/max do not work properly, so we undefine
  * _POSIX_THREAD_PRIORITY_SCHEDULING here.
  */
 #undef _POSIX_THREAD_PRIORITY_SCHEDULING
 #endif
 
+#ifdef _PR_NICE_PRIORITY_SCHEDULING
+#undef _POSIX_THREAD_PRIORITY_SCHEDULING
+#include <sys/resource.h>
+#ifndef HAVE_GETTID
+#define gettid() (syscall(SYS_gettid))
+#endif
+#endif
+
 /*
  * Record whether or not we have the privilege to set the scheduling
  * policy and priority of threads.  0 means that privilege is available.
  * EPERM means that privilege is not available.
  */
 
 static PRIntn pt_schedpriv = 0;
 extern PRLock *_pr_sleeplock;
@@ -49,26 +57,35 @@ static struct _PT_Bookeeping
     PRInt32 minPrio, maxPrio;   /* range of scheduling priorities */
 #endif
 } pt_book = {0};
 
 static void _pt_thread_death(void *arg);
 static void _pt_thread_death_internal(void *arg, PRBool callDestructors);
 static void init_pthread_gc_support(void);
 
-#if defined(_PR_DCETHREADS) || defined(_POSIX_THREAD_PRIORITY_SCHEDULING)
+#if defined(_PR_DCETHREADS) || \
+    defined(_POSIX_THREAD_PRIORITY_SCHEDULING) || \
+    defined(_PR_NICE_PRIORITY_SCHEDULING)
 static PRIntn pt_PriorityMap(PRThreadPriority pri)
 {
 #ifdef NTO
     /* This priority algorithm causes lots of problems on Neutrino
      * for now I have just hard coded everything to run at priority 10
      * until I can come up with a new algorithm.
      *     Jerry.Kirk@Nexwarecorp.com
      */
     return 10;
+#elif defined(_PR_NICE_PRIORITY_SCHEDULING)
+    /* This maps high priorities to low nice values:
+     * PR_PRIORITY_LOW     1
+     * PR_PRIORITY_NORMAL  0
+     * PR_PRIORITY_HIGH   -1
+     * PR_PRIORITY_URGENT -2 */
+    return 1 - pri;
 #else
     return pt_book.minPrio +
 	    pri * (pt_book.maxPrio - pt_book.minPrio) / PR_PRIORITY_LAST;
 #endif
 }
 #endif
 
 /*
@@ -93,28 +110,46 @@ static void _PR_InitializeStack(PRThread
     }
 }
 
 static void *_pt_root(void *arg)
 {
     PRIntn rv;
     PRThread *thred = (PRThread*)arg;
     PRBool detached = (thred->state & PT_THREAD_DETACHED) ? PR_TRUE : PR_FALSE;
+#ifdef _PR_NICE_PRIORITY_SCHEDULING
+    pid_t tid;
+#endif
 
     /*
      * Both the parent thread and this new thread set thred->id.
      * The new thread must ensure that thred->id is set before
      * it executes its startFunc.  The parent thread must ensure
      * that thred->id is set before PR_CreateThread() returns.
      * Both threads set thred->id without holding a lock.  Since
      * they are writing the same value, this unprotected double
      * write should be safe.
      */
     thred->id = pthread_self();
 
+#ifdef _PR_NICE_PRIORITY_SCHEDULING
+    /*
+     * We need to know the kernel thread ID of each thread in order to
+     * set its priority hence we do it here instead of at creation time.
+     */
+    tid = gettid();
+
+    rv = setpriority(PRIO_PROCESS, tid, pt_PriorityMap(thred->priority));
+
+    PR_Lock(pt_book.ml);
+    thred->tid = tid;
+    PR_NotifyAllCondVar(pt_book.cv);
+    PR_Unlock(pt_book.ml);
+#endif
+
     /*
     ** DCE Threads can't detach during creation, so do it late.
     ** I would like to do it only here, but that doesn't seem
     ** to work.
     */
 #if defined(_PR_DCETHREADS)
     if (detached)
     {
@@ -219,16 +254,19 @@ static PRThread* pt_AttachThread(void)
     /* PR_NEWZAP must not call PR_GetCurrentThread() */
     thred = PR_NEWZAP(PRThread);
     if (NULL != thred)
     {
         int rv;
 
         thred->priority = PR_PRIORITY_NORMAL;
         thred->id = pthread_self();
+#ifdef _PR_NICE_PRIORITY_SCHEDULING
+        thred->tid = gettid();
+#endif
         rv = pthread_setspecific(pt_book.key, thred);
         PR_ASSERT(0 == rv);
 
         thred->state = PT_THREAD_GLOBAL | PT_THREAD_FOREIGN;
         PR_Lock(pt_book.ml);
 
         /* then put it into the list */
         thred->prev = pt_book.last;
@@ -639,16 +677,31 @@ PR_IMPLEMENT(void) PR_SetThreadPriority(
 				pt_schedpriv = EPERM;
 				PR_LOG(_pr_thread_lm, PR_LOG_MIN,
 					("PR_SetThreadPriority: no thread scheduling privilege"));
 			}
 		}
 		if (rv != 0)
 			rv = -1;
     }
+#elif defined(_PR_NICE_PRIORITY_SCHEDULING)
+    PR_Lock(pt_book.ml);
+    while (thred->tid == 0)
+        PR_WaitCondVar(pt_book.cv, PR_INTERVAL_NO_TIMEOUT);
+    PR_Unlock(pt_book.ml);
+
+    rv = setpriority(PRIO_PROCESS, thred->tid, pt_PriorityMap(newPri));
+
+    if (rv == -1 && errno == EPERM)
+    {
+        /* We don't set pt_schedpriv to EPERM because adjusting the nice
+         * value might be permitted for certain ranges but not others */
+        PR_LOG(_pr_thread_lm, PR_LOG_MIN,
+            ("PR_SetThreadPriority: no thread scheduling privilege"));
+    }
 #endif
 
     thred->priority = newPri;
 }  /* PR_SetThreadPriority */
 
 PR_IMPLEMENT(PRStatus) PR_Interrupt(PRThread *thred)
 {
     /*
@@ -857,16 +910,19 @@ void _PR_InitThreads(
     pt_book.cv = PR_NewCondVar(pt_book.ml);
     PR_ASSERT(NULL != pt_book.cv);
     thred = PR_NEWZAP(PRThread);
     PR_ASSERT(NULL != thred);
     thred->arg = NULL;
     thred->startFunc = NULL;
     thred->priority = priority;
     thred->id = pthread_self();
+#ifdef _PR_NICE_PRIORITY_SCHEDULING
+    thred->tid = gettid();
+#endif
 
     thred->state = (PT_THREAD_DETACHED | PT_THREAD_PRIMORD);
     if (PR_SYSTEM_THREAD == type)
     {
         thred->state |= PT_THREAD_SYSTEM;
         pt_book.system += 1;
 	    pt_book.this_many = 0;
     }