Experimental Physics and Industrial Control System
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
<2020>
2021
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
<2020>
2021
2022
2023
2024