EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024  Index 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0
From: Dirk Zimoch via Core-talk <core-talk at aps.anl.gov>
To: mp+394327 at code.launchpad.net
Date: Mon, 23 Nov 2020 13:42:23 -0000
Dirk Zimoch has proposed merging ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0.

Requested reviews:
  EPICS Core Developers (epics-core)

For more details, see:
https://code.launchpad.net/~dirk.zimoch/epics-base/+git/epics-base/+merge/394327

This change enables priority inheritance for posix mutexes and thus prevents priority inversion problems on real-time Linux systems.

For backward compatibility reasons, this feature must be explicitly enabled by setting the environment variable EPICS_MUTEX_USE_PRIORITY_INHERITANCE=YES before libCom is loaded.

Is only meaningful when running with SCHED_FIFO scheduling, which requires sufficient priviledges to set. That typically means to run the IOC as root.

-- 
Your team EPICS Core Developers is requested to review the proposed merge of ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0.
diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md
index e6234c8..9ddee5c 100644
--- a/documentation/RELEASE_NOTES.md
+++ b/documentation/RELEASE_NOTES.md
@@ -17,6 +17,14 @@ should also be read to understand what has changed since earlier releases.
 
 <!-- Insert new items immediately below here ... -->
 
+### Priority inversion safe posix mutexes
+
+On Posix systems, epicsMutex now support priority inheritance.
+For beward compatibility, the environment variable
+`EPICS_MUTEX_USE_PRIORITY_INHERITANCE` must be set to `YES` before
+the IOC is started to use this feature. The IOC needs to run with
+SCHED_FIFO engaged, which usually means to run it as root.
+
 ### Filters in database input links
 
 Input database links can now use channel filters, it is not necessary to
diff --git a/modules/libcom/src/env/envDefs.h b/modules/libcom/src/env/envDefs.h
index 3bf64c8..159154b 100644
--- a/modules/libcom/src/env/envDefs.h
+++ b/modules/libcom/src/env/envDefs.h
@@ -76,6 +76,7 @@ LIBCOM_API extern const ENV_PARAM EPICS_IOC_LOG_FILE_COMMAND;
 LIBCOM_API extern const ENV_PARAM IOCSH_PS1;
 LIBCOM_API extern const ENV_PARAM IOCSH_HISTSIZE;
 LIBCOM_API extern const ENV_PARAM IOCSH_HISTEDIT_DISABLE;
+LIBCOM_API extern const ENV_PARAM EPICS_MUTEX_USE_PRIORITY_INHERITANCE;
 LIBCOM_API extern const ENV_PARAM *env_param_list[];
 
 struct in_addr;
diff --git a/modules/libcom/src/osi/os/posix/osdEvent.c b/modules/libcom/src/osi/os/posix/osdEvent.c
index 251f35d..ffe307e 100644
--- a/modules/libcom/src/osi/os/posix/osdEvent.c
+++ b/modules/libcom/src/osi/os/posix/osdEvent.c
@@ -23,6 +23,7 @@
 #include "epicsEvent.h"
 #include "epicsTime.h"
 #include "errlog.h"
+#include "osdMutex.h"
 
 struct epicsEventOSD {
     pthread_mutex_t mutex;
@@ -50,11 +51,11 @@ LIBCOM_API epicsEventId epicsEventCreate(epicsEventInitialState init)
     epicsEventId pevent = malloc(sizeof(*pevent));
 
     if (pevent) {
-        int status = pthread_mutex_init(&pevent->mutex, 0);
+        int status = epicsPosixMutexInit(&pevent->mutex, posixMutexDefault);
 
         pevent->isFull = (init == epicsEventFull);
         if (status) {
-            printStatus(status, "pthread_mutex_init", "epicsEventCreate");
+            printStatus(status, "epicsPosixMutexInit", "epicsEventCreate");
         } else {
             status = pthread_cond_init(&pevent->cond, 0);
             if (!status)
diff --git a/modules/libcom/src/osi/os/posix/osdMutex.c b/modules/libcom/src/osi/os/posix/osdMutex.c
index 707ae63..f344016 100644
--- a/modules/libcom/src/osi/os/posix/osdMutex.c
+++ b/modules/libcom/src/osi/os/posix/osdMutex.c
@@ -19,12 +19,14 @@
 #include <errno.h>
 #include <unistd.h>
 #include <pthread.h>
+#include <ctype.h>
 
 #include "epicsMutex.h"
 #include "cantProceed.h"
 #include "epicsTime.h"
 #include "errlog.h"
 #include "epicsAssert.h"
+#include "envDefs.h"
 
 #define checkStatus(status,message) \
     if((status)) { \
@@ -38,6 +40,105 @@
         cantProceed((method)); \
     }
 
+/* Until these can be demonstrated to work leave them undefined*/
+/* On solaris 8 _POSIX_THREAD_PRIO_INHERIT fails*/
+#if defined(DONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING)
+#undef _POSIX_THREAD_PRIO_INHERIT
+#endif
+
+#if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500
+#define HAVE_RECURSIVE_MUTEX
+#else
+#undef  HAVE_RECURSIVE_MUTEX
+#endif
+
+/* Global var - pthread_once does not support passing args but it is more efficient
+ * then epicsThreadOnce which always acquires a mutex.
+ */
+static pthread_mutexattr_t globalAttrDefault;
+#ifdef HAVE_RECURSIVE_MUTEX
+static pthread_mutexattr_t globalAttrRecursive;
+#endif
+static pthread_once_t      globalAttrInitOnce = PTHREAD_ONCE_INIT;
+
+static void setAttrDefaults(pthread_mutexattr_t *a)
+{
+    int status;
+
+    status = pthread_mutexattr_init(a);
+    checkStatusQuit(status,"pthread_mutexattr_init","setAttrDefaults");
+
+    {
+        const char *p = envGetConfigParamPtr(&EPICS_MUTEX_USE_PRIORITY_INHERITANCE);
+        char        c = p ? toupper(p[0]) : 'N';
+        if ( 'T' == c || 'Y' == c || '1' == c ) {
+#if defined _POSIX_THREAD_PRIO_INHERIT
+            status = pthread_mutexattr_setprotocol(a, PTHREAD_PRIO_INHERIT);
+            if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocol(PTHREAD_PRIO_INHERIT)");
+#ifndef HAVE_RECURSIVE_MUTEX
+            /* The implementation based on a condition variable below does not support 
+             * priority-inheritance!
+             */
+            fprintf(stderr,"WARNING: PRIORITY-INHERITANCE UNAVAILABLE for epicsMutex\n");
+#endif
+#else
+            fprintf(stderr,"WARNING: PRIORITY-INHERITANCE UNAVAILABLE OR NOT COMPILED IN\n");
+#endif
+        }
+    }
+}
+
+static void globalAttrInit()
+{
+    int status;
+
+    setAttrDefaults( &globalAttrDefault );
+
+#ifdef HAVE_RECURSIVE_MUTEX
+    setAttrDefaults( &globalAttrRecursive );
+    status = pthread_mutexattr_settype(&globalAttrRecursive, PTHREAD_MUTEX_RECURSIVE);
+    checkStatusQuit(status, "pthread_mutexattr_settype(PTHREAD_MUTEX_RECURSIVE)", "globalAttrInit");
+#endif
+}
+
+epicsShareFunc pthread_mutexattr_t * epicsShareAPI epicsPosixMutexAttrGet (EpicsPosixMutexProperty p)
+{
+    int status;
+
+    status = pthread_once( &globalAttrInitOnce, globalAttrInit );
+    checkStatusQuit(status,"pthread_once","epicsPosixMutexAttrGet");
+    switch ( p ) {
+        default:
+        case posixMutexDefault:
+            break;
+        case posixMutexRecursive:
+#ifdef HAVE_RECURSIVE_MUTEX
+            return &globalAttrRecursive;
+#else
+            return 0;
+#endif
+    }
+    return &globalAttrDefault;
+}
+
+epicsShareFunc int epicsShareAPI epicsPosixMutexInit (pthread_mutex_t *m, EpicsPosixMutexProperty p)
+{
+    pthread_mutexattr_t *atts = epicsPosixMutexAttrGet( p );
+
+    if ( ! atts )
+        return ENOTSUP;
+    return pthread_mutex_init(m, atts);
+}
+
+epicsShareFunc void epicsShareAPI epicsPosixMutexMustInit (pthread_mutex_t *m, EpicsPosixMutexProperty p)
+{
+    int status;
+
+    status = epicsPosixMutexInit(m, p);
+    checkStatusQuit(status,"pthread_mutex_init","epicsMustInitPosixMutex");
+}
+
+
 static int mutexLock(pthread_mutex_t *id)
 {
     int status;
@@ -48,11 +149,6 @@ static int mutexLock(pthread_mutex_t *id)
     return status;
 }
 
-/* Until these can be demonstrated to work leave them undefined*/
-/* On solaris 8 _POSIX_THREAD_PRIO_INHERIT fails*/
-#undef _POSIX_THREAD_PROCESS_SHARED
-#undef _POSIX_THREAD_PRIO_INHERIT
-
 /* Two completely different implementations are provided below
  * If support is available for PTHREAD_MUTEX_RECURSIVE then
  *      only pthread_mutex is used.
@@ -61,10 +157,9 @@ static int mutexLock(pthread_mutex_t *id)
  */
 
 
-#if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500
+#ifdef HAVE_RECURSIVE_MUTEX
 typedef struct epicsMutexOSD {
     pthread_mutex_t     lock;
-    pthread_mutexattr_t mutexAttr;
 } epicsMutexOSD;
 
 epicsMutexOSD * epicsMutexOsdCreate(void) {
@@ -73,32 +168,12 @@ epicsMutexOSD * epicsMutexOsdCreate(void) {
 
     pmutex = calloc(1, sizeof(*pmutex));
     if(!pmutex)
-        goto fail;
-
-    status = pthread_mutexattr_init(&pmutex->mutexAttr);
-    if (status)
-        goto fail;
-
-#if defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0
-    status = pthread_mutexattr_setprotocol(&pmutex->mutexAttr,
-        PTHREAD_PRIO_INHERIT);
-    if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocal");
-#endif /*_POSIX_THREAD_PRIO_INHERIT*/
-
-    status = pthread_mutexattr_settype(&pmutex->mutexAttr,
-        PTHREAD_MUTEX_RECURSIVE);
-    checkStatus(status, "pthread_mutexattr_settype");
-    if (status)
-        goto fail;
-
-    status = pthread_mutex_init(&pmutex->lock, &pmutex->mutexAttr);
-    if (status)
-        goto dattr;
-    return pmutex;
-
-dattr:
-    pthread_mutexattr_destroy(&pmutex->mutexAttr);
-fail:
+        return NULL;
+
+    status = epicsPosixMutexInit(&pmutex->lock, posixMutexRecursive);
+    if (!status)
+        return pmutex;
+
     free(pmutex);
     return NULL;
 }
@@ -109,8 +184,6 @@ void epicsMutexOsdDestroy(struct epicsMutexOSD * pmutex)
 
     status = pthread_mutex_destroy(&pmutex->lock);
     checkStatus(status, "pthread_mutex_destroy");
-    status = pthread_mutexattr_destroy(&pmutex->mutexAttr);
-    checkStatus(status, "pthread_mutexattr_destroy");
     free(pmutex);
 }
 
@@ -158,15 +231,83 @@ void epicsMutexOsdShow(struct epicsMutexOSD * pmutex, unsigned int level)
     printf("    pthread_mutex_t* uaddr=%p\n", &pmutex->lock);
 }
 
-#else /*defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500 */
+#else /* #ifdef HAVE_RECURSIVE_MUTEX */
+
+/* The standard EPICS implementation of a recursive mutex (in absence of native support)
+ * does not allow for priority-inheritance:
+ * a low priority thread may hold the ('soft-') mutex, i.e., may be preempted in the
+ * critical section without the high-priority thread noticing (because the HP-thread is
+ * sleeping on the condvar and not waiting for the mutex).
+ *
+ * A better implementation could be:
+ *
+ *  struct epicsMutexOSD {
+ *    pthread_mutex_t   mtx;
+ *    atomic<pthread_t> owner;
+ *    unsigned          count;
+ *  };
+ *
+ * void mutexLock(struct epicsMutexOSD *m)
+ * {
+ *   pthread_t currentOwner = atomic_load(&m->owner, acquire);   
+ *
+ *   if ( pthread_equal(currentOwner, pthread_self()) ) {
+ *     m->count++;
+ *     return;
+ *   }
+ *
+ *   pthread_mutex_lock(&m->mtx);
+ *   // ordering of this write to 'owner' is irrelevant since it doesn't matter
+ *   // if another thread performing the test above sees the 'invalid' or already
+ *   // 'our' TID. 
+ *   atomic_store(&m->owner, pthread_self(), relaxed);
+ *   // 'count' is only ever accessed with 'mtx' held
+ *   m->count = 1;
+ * }
+ *
+ * void mutexUnlock(struct epicsMutexOSD *o)
+ * {
+ *   o->count--;
+ *   if ( o->count == 0 ) {
+ *     // acquire-release ordering between here and 'mutexLock' above'!
+ *     // Theoretically (but extremely unlikely) the executing thread
+ *     // may go away and a newly created thread with the same (recycled)
+ *     // TID on a different CPU could still see the old TID in mutexLock
+ *     // and believe it already owns the mutex...
+ *     atomic_store(&m->owner, invalid_thread_id, release);
+ *     pthread_mutex_unlock( &o->mtx );
+ *   }
+ *
+ * The 'invalid_thread_id' could be an ID of a permanently suspended dummy thread
+ * (pthread does not define a 'NULL' ID and you don't want to get into an 'ABA'-sort
+ * of situation where 'mutexLock' believes to be the current owner because the 'invalid'
+ * ID is a 'recycled' thread id).
+ *
+ * Without atomic operations we'd have to introduce a second mutex to protect the 'owner'
+ * member ('count' is only ever accessed with the mutex held). But that would then
+ * lead to two extra lock/unlock pairs in 'mutexLock'. A dirty version would ignore that
+ * and rely on pthread_t fitting in a CPU word and the acquire/release corner-case mentioned
+ * above to never happen. Plus, some CPUs (x86) are more strongly (acq/rel) ordered implicitly.
+ *
+ *   Here the corner case again:
+ *
+ *        CPU1                  CPU2
+ *     owner = 1234
+ *     ...
+ *     owner = invalid_tid
+ *     mutex_unlock()
+ *
+ *     thread 1234 dies         new thread with recycled TID 1234
+ *                              enters osdMutexLock
+ *                              'owner=invalid_tid' assignment not yet visible on this CPU
+ *                              if ( pthread_equal( owner, pthread_self() ) ) {
+ *                                    ==> ERRONEOUSLY ENTERS HERE
+ *                              }
+ */
 
 typedef struct epicsMutexOSD {
     pthread_mutex_t     lock;
-    pthread_mutexattr_t mutexAttr;
     pthread_cond_t      waitToBeOwner;
-#if defined(_POSIX_THREAD_PROCESS_SHARED) && _POSIX_THREAD_PROCESS_SHARED > 0
-    pthread_condattr_t  condAttr;
-#endif /*_POSIX_THREAD_PROCESS_SHARED*/
     int                 count;
     int                 owned;  /* TRUE | FALSE */
     pthread_t           ownerTid;
@@ -180,40 +321,13 @@ epicsMutexOSD * epicsMutexOsdCreate(void) {
     if(!pmutex)
         return NULL;
 
-    status = pthread_mutexattr_init(&pmutex->mutexAttr);
-    if(status)
-        goto fail;
-
-#if defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0
-    status = pthread_mutexattr_setprotocol(
-        &pmutex->mutexAttr,PTHREAD_PRIO_INHERIT);
-    if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocal");
-#endif /*_POSIX_THREAD_PRIO_INHERIT*/
+    epicsPosixMutexMustInit(&pmutex->lock, posixMutexDefault);
 
-    status = pthread_mutex_init(&pmutex->lock, &pmutex->mutexAttr);
-    if(status)
-        goto dattr;
-
-#if defined(_POSIX_THREAD_PROCESS_SHARED) && _POSIX_THREAD_PROCESS_SHARED > 0
-    status = pthread_condattr_init(&pmutex->condAttr);
-    checkStatus(status, "pthread_condattr_init");
-    status = pthread_condattr_setpshared(&pmutex->condAttr,
-        PTHREAD_PROCESS_PRIVATE);
-    checkStatus(status, "pthread_condattr_setpshared");
-    status = pthread_cond_init(&pmutex->waitToBeOwner, &pmutex->condAttr);
-#else
     status = pthread_cond_init(&pmutex->waitToBeOwner, 0);
-#endif /*_POSIX_THREAD_PROCESS_SHARED*/
-    if(status)
-        goto dmutex;
-
-    return pmutex;
+    if(!status)
+        return pmutex;
 
-dmutex:
     pthread_mutex_destroy(&pmutex->lock);
-dattr:
-    pthread_mutexattr_destroy(&pmutex->mutexAttr);
-fail:
     free(pmutex);
     return NULL;
 }
@@ -224,13 +338,8 @@ void epicsMutexOsdDestroy(struct epicsMutexOSD * pmutex)
 
     status = pthread_cond_destroy(&pmutex->waitToBeOwner);
     checkStatus(status, "pthread_cond_destroy");
-#if defined(_POSIX_THREAD_PROCESS_SHARED) && _POSIX_THREAD_PROCESS_SHARED > 0
-    status = pthread_condattr_destroy(&pmutex->condAttr);
-#endif /*_POSIX_THREAD_PROCESS_SHARED*/
     status = pthread_mutex_destroy(&pmutex->lock);
     checkStatus(status, "pthread_mutex_destroy");
-    status = pthread_mutexattr_destroy(&pmutex->mutexAttr);
-    checkStatus(status, "pthread_mutexattr_destroy");
     free(pmutex);
 }
 
@@ -331,4 +440,4 @@ void epicsMutexOsdShow(struct epicsMutexOSD *pmutex,unsigned int level)
     printf("ownerTid %p count %d owned %d\n",
         (void *)pmutex->ownerTid, pmutex->count, pmutex->owned);
 }
-#endif /*defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500 */
+#endif /* #ifdef HAVE_RECURSIVE_MUTEX */
diff --git a/modules/libcom/src/osi/os/posix/osdMutex.h b/modules/libcom/src/osi/os/posix/osdMutex.h
index 699e47f..9480bb8 100644
--- a/modules/libcom/src/osi/os/posix/osdMutex.h
+++ b/modules/libcom/src/osi/os/posix/osdMutex.h
@@ -8,3 +8,26 @@
 * in file LICENSE that is included with this distribution.
 \*************************************************************************/
 /* for a pure posix implementation no osdMutex.h definitions are needed*/
+#ifndef osdMutexh
+#define osdMutexh
+
+#include <pthread.h>
+
+#include "shareLib.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {posixMutexDefault = 0, posixMutexRecursive = 1} EpicsPosixMutexProperty;
+
+/* Returns NULL if requested set of properties is not supported */
+epicsShareFunc pthread_mutexattr_t * epicsShareAPI epicsPosixMutexAttrGet (EpicsPosixMutexProperty);
+epicsShareFunc int                   epicsShareAPI epicsPosixMutexInit    (pthread_mutex_t *,EpicsPosixMutexProperty);
+epicsShareFunc void                  epicsShareAPI epicsPosixMutexMustInit(pthread_mutex_t *,EpicsPosixMutexProperty);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* osdMutexh */
diff --git a/modules/libcom/src/osi/os/posix/osdSpin.c b/modules/libcom/src/osi/os/posix/osdSpin.c
index 1f8434b..a65937e 100644
--- a/modules/libcom/src/osi/os/posix/osdSpin.c
+++ b/modules/libcom/src/osi/os/posix/osdSpin.c
@@ -109,6 +109,8 @@ void epicsSpinUnlock(epicsSpinId spin) {
  *  POSIX MUTEX IMPLEMENTATION
  */
 
+#include <osdMutex.h>
+
 typedef struct epicsSpin {
     pthread_mutex_t lock;
 } epicsSpin;
@@ -121,7 +123,7 @@ epicsSpinId epicsSpinCreate(void) {
     if (!spin)
         goto fail;
 
-    status = pthread_mutex_init(&spin->lock, NULL);
+    status = epicsPosixMutexInit(&spin->lock, posixMutexDefault);
     checkStatus(status, "pthread_mutex_init");
     if (status)
         goto fail;
diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c
index d7aa3c4..b25641e 100644
--- a/modules/libcom/src/osi/os/posix/osdThread.c
+++ b/modules/libcom/src/osi/os/posix/osdThread.c
@@ -327,10 +327,10 @@ static void once(void)
     int status;
 
     pthread_key_create(&getpthreadInfo,0);
-    status = pthread_mutex_init(&onceLock,0);
-    checkStatusQuit(status,"pthread_mutex_init","epicsThreadInit");
-    status = pthread_mutex_init(&listLock,0);
-    checkStatusQuit(status,"pthread_mutex_init","epicsThreadInit");
+    status = epicsPosixMutexInit(&onceLock,posixMutexDefault);
+    checkStatusQuit(status,"epicsPosixMutexInit","epicsThreadInit");
+    status = epicsPosixMutexInit(&listLock,posixMutexDefault);
+    checkStatusQuit(status,"epicsPosixMutexInit","epicsThreadInit");
     pcommonAttr = calloc(1,sizeof(commonAttr));
     if(!pcommonAttr) checkStatusOnceQuit(errno,"calloc","epicsThreadInit");
     status = pthread_attr_init(&pcommonAttr->attr);
diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile
index 3ad869a..f60c05b 100755
--- a/modules/libcom/test/Makefile
+++ b/modules/libcom/test/Makefile
@@ -333,6 +333,11 @@ TESTS += nonEpicsThreadPriorityTest
 endif
 endif
 
+TESTPROD_HOST_Linux += epicsMutexPriorityInversionTest
+epicsMutexPriorityInversionTest_SRCS += epicsMutexPriorityInversionTest.c
+epicsMutexPriorityInversionTest_SYS_LIBS_Linux += pthread rt
+TESTSCRIPTS_HOST_Linux+=epicsMutexPriorityInversionTest.t
+
 include $(TOP)/configure/RULES
 
 rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl
diff --git a/modules/libcom/test/epicsMutexPriorityInversionTest.c b/modules/libcom/test/epicsMutexPriorityInversionTest.c
new file mode 100644
index 0000000..90b61ea
--- /dev/null
+++ b/modules/libcom/test/epicsMutexPriorityInversionTest.c
@@ -0,0 +1,266 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <features.h>
+#include <sched.h>
+#include <epicsThread.h>
+#include <epicsEvent.h>
+#include <epicsMutex.h>
+#include <epicsUnitTest.h>
+#include <errlog.h>
+#include <testMain.h>
+#include <time.h>
+#include <envDefs.h>
+#include <stdio.h>
+
+#define  THE_CPU   0
+
+#define  SPIN_SECS 3000000
+
+#if defined(__GLIBC__) && defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2,4)
+#define HAVE_CPU_AFFINITY
+#else
+#undef  HAVE_CPU_AFFINITY
+#endif
+
+typedef struct ThreadArg_ {
+    epicsEventId           sync;
+    epicsEventId           done;
+    epicsMutexId           mtx;
+    volatile unsigned long end;
+    unsigned long          lim;
+    int                   pri[3];
+} ThreadArg;
+
+static unsigned long getClockUs()
+{
+    struct timespec now;
+    if ( clock_gettime( CLOCK_MONOTONIC, &now ) ) {
+        testAbort("clock_gettime(CLOCK_MONOTONIC) failed");
+    }
+    return ((unsigned long)now.tv_sec)*1000000 + ((unsigned long)now.tv_nsec)/1000;
+}
+
+/*
+ * Set affinity of executing thread to 'cpu'
+ */
+static void setThreadAffinity(int cpu)
+{
+#ifdef HAVE_CPU_AFFINITY
+    cpu_set_t cset;
+
+    CPU_ZERO( &cset );
+    CPU_SET( cpu, &cset );
+    testOk1 ( 0 == pthread_setaffinity_np( pthread_self(), sizeof(cset), &cset ) );
+#endif
+}
+
+/*
+ * Ensure only 'cpu' is set in executing thread's affinity mask
+ */
+static void checkAffinity(int cpu)
+{
+#ifdef HAVE_CPU_AFFINITY
+    cpu_set_t cset;
+
+    if ( pthread_getaffinity_np( pthread_self(), sizeof(cset), &cset) ) {
+        testFail("pthread_getaffinity_np FAILED");
+        return;
+    }
+    testOk ( 1 == CPU_COUNT( &cset ) && CPU_ISSET( cpu, &cset ), "Checking CPU affinity mask" );
+#endif
+}
+
+
+/*
+ * Make sure executing thread uses SCHED_FIFO and
+ * store its priority a->pri[idx]
+ *
+ * RETURNS: 0 if SCHED_FIFO engaged, nonzero otherwise.
+ */
+static int checkThreadPri(ThreadArg *a, unsigned idx)
+{
+    int                pol;
+    struct sched_param p;
+
+    if ( pthread_getschedparam( pthread_self(), &pol, &p ) ) {
+        testFail("pthread_getschedparam FAILED");
+        return 1;
+    }
+    if ( a && idx < sizeof(a->pri)/sizeof(a->pri[0]) ) {
+        a->pri[idx] = p.sched_priority;
+    }
+    testOk1( SCHED_FIFO == pol );
+    return (SCHED_FIFO != pol);
+}
+
+
+/*
+ * Low-priority thread.
+ *
+ * Lock mutex and signal to the medium-priority thread
+ * that it may proceed.
+ *
+ * Since all three (high-, medium- and low-priority threads)
+ * execute on the same CPU (via affinity) the medium-
+ * priority thread will then take over the CPU and prevent
+ * (unless priority-inheritance is enabled, that is)
+ * the low-priority thread from continueing on to unlock
+ * the mutex.
+ */
+static void loPriThread(void *parm)
+{
+    ThreadArg *a = (ThreadArg*)parm;
+
+    checkAffinity( THE_CPU );
+
+    checkThreadPri( a, 0 );
+
+    epicsMutexMustLock( a->mtx );
+
+    epicsEventSignal( a->sync );
+
+    /* medium-priority thread takes over CPU and spins
+     * while the high-priority thread waits for the mutex.
+     * With priority-inheritance enabled this thread's
+     * priority will be increased so that the medium-
+     * priority thread can be preempted and we proceed
+     * to release the mutex.
+     */
+
+    epicsMutexUnlock( a->mtx );
+}
+
+static void hiPriThread(void *parm)
+{
+    ThreadArg  *a = (ThreadArg*)parm;
+
+    /* Try to get the mutex */
+    epicsMutexMustLock( a->mtx );
+    /* Record the time when we obtained the mutex */
+    a->end = getClockUs();
+    epicsMutexUnlock( a->mtx );
+    /* Tell the main thread that the test done */
+    epicsEventSignal( a->done );
+}
+
+static void miPriThread(void *parm)
+{
+    ThreadArg  *a = (ThreadArg*)parm;
+
+    /* Basic checks:
+     *  - affinity must be set to use single CPU for all threads
+     *  - SCHED_FIFO must be in effect
+     */
+    checkAffinity( THE_CPU );
+    checkThreadPri( a, 1 );
+
+    /* Create the low-priority thread. */
+    epicsThreadMustCreate("testLo",
+                          a->pri[0],
+                          epicsThreadGetStackSize( epicsThreadStackMedium ),
+                          loPriThread,
+                          a);
+
+    /* Wait until low-priority thread has taken the mutex */
+    epicsEventMustWait( a->sync );
+
+    /* Compute the end-time for our spinning loop */
+    a->lim = getClockUs() + SPIN_SECS;
+    a->end = 0;
+
+    /* Create the high-priority thread. The hiPri thread will
+     * block for the mutex and
+     *  if priority-inheritance is available:
+     *    increase the low-priority thread's priority temporarily
+     *    so it can proceed to release the mutex and hand it to
+     *    the high-priority thread.
+     *  if priority-inheritance is not available:
+     *    the high-priority thread will have to wait until we are
+     *    done spinning and the low-priority thread is scheduled
+     *    again.
+     */
+    epicsThreadMustCreate("testHi",
+                          a->pri[2],
+                          epicsThreadGetStackSize( epicsThreadStackMedium ),
+                          hiPriThread,
+                          a);
+
+    /* Spin for some time; the goal is hogging the CPU and thus preventing
+     * the low-priority thread from being scheduled.
+     * If priority-inheritance is enabled then the low-priority thread's
+     * priority is temporarily increased so that we can be preempted. This
+     * then causes the high-priority thread to record the 'end' time which
+     * tells us that we can terminate early...
+     */
+    while ( 0 == a->end && getClockUs() < a->lim )
+        /* spin */;
+
+    /* w/o priority-inheritance the low-priority thread may proceed at
+     * this point and release the mutex to the high-priority thread.
+     */
+}
+
+
+#define NUM_TESTS 8
+
+MAIN(epicsMutexPriorityInversionTest)
+{
+    ThreadArg a;
+    long      hiPriStalledTimeUs;
+    struct    sched_param p_pri;
+
+    /* This happens too late - i.e., after initialization of libCom
+     * user must set in the environment...
+    epicsEnvSet("EPICS_MUTEX_USE_PRIORITY_INHERITANCE","YES");
+     */
+
+    a.mtx  = epicsMutexMustCreate();
+    a.sync = epicsEventMustCreate( epicsEventEmpty );
+    a.done = epicsEventMustCreate( epicsEventEmpty );
+    a.pri[0] = epicsThreadPriorityLow;
+    a.pri[1] = epicsThreadPriorityMedium;
+    a.pri[2] = epicsThreadPriorityHigh;
+
+    testPlan(NUM_TESTS);
+
+#ifndef HAVE_CPU_AFFINITY
+#warning "glibc too old for this test: pthread_setaffinity_np() not available..."
+    testSkip( NUM_TESTS, "glibc too old for this test: pthread_setaffinity_np() not implemented!" );
+    return testDone();
+#endif
+
+    p_pri.sched_priority = sched_get_priority_min( SCHED_FIFO );
+    if ( sched_setscheduler( 0, SCHED_FIFO, &p_pri ) ) {
+        testDiag("SCHED_FIFO not engaged - maybe you need to be root to run this test?");
+        testFail("sched_setscheduler(SCHED_FIFO)");
+        testSkip( NUM_TESTS - 1, "SCHED_FIFO not engaged" );
+        return testDone();
+    } else {
+        testPass("SCHED_FIFO can be used");
+    }
+
+    setThreadAffinity( THE_CPU );
+    /* created threads should inherit CPU affinity mask */
+
+    epicsThreadMustCreate("testMi",
+                          a.pri[1],
+                          epicsThreadGetStackSize( epicsThreadStackMedium ),
+                          miPriThread,
+                          &a);
+
+    epicsEventMustWait( a.done );
+
+    testOk1( (a.pri[0] < a.pri[1]) && (a.pri[1] < a.pri[2]) );
+
+    hiPriStalledTimeUs = a.end - (a.lim - SPIN_SECS);
+
+    testDiag("High-priority thread stalled for %li us\n", hiPriStalledTimeUs);
+    testOk1( hiPriStalledTimeUs <  200 );
+
+    epicsEventDestroy( a.done );
+    epicsEventDestroy( a.sync );
+    epicsMutexDestroy( a.mtx  );
+
+    return testDone();
+}
diff --git a/modules/libcom/test/osiSockTest.c b/modules/libcom/test/osiSockTest.c
index f2adb41..bce26e2 100644
--- a/modules/libcom/test/osiSockTest.c
+++ b/modules/libcom/test/osiSockTest.c
@@ -238,8 +238,11 @@ void udpSockFanoutTestRx(void* raw)
         union CASearchU buf;
         osiSockAddr src;
         osiSocklen_t srclen = sizeof(src);
+        int n;
 
-        int n = recvfrom(info->sock, buf.bytes, sizeof(buf.bytes), 0, &src.sa, &srclen);
+        epicsTimeGetCurrent(&now);
+
+        n = recvfrom(info->sock, buf.bytes, sizeof(buf.bytes), 0, &src.sa, &srclen);
         buf.bytes[sizeof(buf.bytes)-1] = '\0';
 
         if(n<0) {

Replies:
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Ralph Lange via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 mdavidsaver via Core-talk

Navigate by Date:
Prev: Re: How to "tweak" tests? Ralph Lange via Core-talk
Next: Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024 
Navigate by Thread:
Prev: Re: How to "tweak" tests? Ben Franksen via Core-talk
Next: Re: [Merge] ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into epics-base:7.0 Dirk Zimoch via Core-talk
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024 
ANJ, 27 Dec 2020 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·