Merge ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance into ~epics-core/epics-base/+git/epics-base:7.0
- Git
- lp:~dirk.zimoch/epics-base
- epicsMutexPriorityInheritance
- Merge into 7.0
Status: | Merged |
---|---|
Merge reported by: | mdavidsaver |
Merged at revision: | 4855d9e082e2191b2517822d8466e6148e5f6994 |
Proposed branch: | ~dirk.zimoch/epics-base:epicsMutexPriorityInheritance |
Merge into: | ~epics-core/epics-base/+git/epics-base:7.0 |
Diff against target: |
780 lines (+390/-225) (has conflicts) 9 files modified
documentation/RELEASE_NOTES.md (+9/-0) modules/libcom/src/osi/Makefile (+1/-0) modules/libcom/src/osi/os/posix/osdEvent.c (+3/-2) modules/libcom/src/osi/os/posix/osdMutex.c (+73/-217) modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h (+28/-0) modules/libcom/src/osi/os/posix/osdSpin.c (+4/-2) modules/libcom/src/osi/os/posix/osdThread.c (+5/-4) modules/libcom/test/Makefile (+9/-0) modules/libcom/test/epicsMutexPriorityInversionTest.c (+258/-0) Conflict in documentation/RELEASE_NOTES.md |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
mdavidsaver | Approve | ||
Dirk Zimoch (community) | Approve | ||
Andrew Johnson | Needs Fixing | ||
Review via email: mp+394327@code.launchpad.net |
Commit message
Description of the change
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_
Is only meaningful when running with SCHED_FIFO scheduling, which requires sufficient priviledges to set. That typically means to run the IOC as root.
- e8a0c17... by Till Straumann
-
Support run-time enabled priority-
inheritance for epicsMutex, epicsEvent etc.
Ralph Lange (ralph-lange) wrote : | # |
> ...SCHED_FIFO scheduling, which requires sufficient privileges to set. That typically means to run the IOC as root.
FWIW, IOCs should *not* be run as root.
The EPICS website has detailed instructions at
https:/
mdavidsaver (mdavidsaver) wrote : | # |
I see in posix/osdMutex.c that
> /* Until these can be demonstrated to work leave them undefined*/
> /* On solaris 8 _POSIX_
> #undef _POSIX_
> #undef _POSIX_
has been present since 2002. I would naively think that the various sites using RTLinux flavors which have noticed quickly if epicsMutex didn't have priority inheritance.
Can Dirk (or anyone) give some background on why this change is being proposed now?
Also, some significant complexity is being added in the !HAVE_RECURSIVE
Dirk Zimoch (dirk.zimoch) wrote : | # |
We found that EPICS was not behaving as "real-time" as expected even though SCHED_FIFO was used. A deeper investigation found that this was due to prioriy inversion. When reading the source code, we found that "Until these can be demonstrated to work leave them undefined" has been in for a long time and nobody seemed to have cared, which had surprized us a lot. I think this wither got forgotten somehow and nobody really needed it or noticed it or maybe people simply removed those #undefs (which is not sufficient because other code used posix semaphores directly instead of using epicsMutexes).
I propose this change now, because we have fixed it now.
Till needs to comment on details of the implementation...
Dirk Zimoch (dirk.zimoch) wrote : | # |
> this wither got forgotten
* either
mdavidsaver (mdavidsaver) wrote : | # |
I tried the simply minded (early morning) test of removing the two #undef lines. It seems like to have the desired effect. pthread_
Am I missing something obvious?
I'm testing with Debian 10, where _XOPEN_SOURCE=700 (by way of -D_GNU_SOURCE).
Also, what is the motivation to economize pthread_
Till Straumann (tillstraumann) wrote : | # |
> We found that EPICS was not behaving as "real-time" as expected even though
> SCHED_FIFO was used. A deeper investigation found that this was due to prioriy
> inversion. When reading the source code, we found that "Until these can be
> demonstrated to work leave them undefined" has been in for a long time and
> nobody seemed to have cared, which had surprized us a lot. I think this wither
> got forgotten somehow and nobody really needed it or noticed it or maybe
> people simply removed those #undefs (which is not sufficient because other
> code used posix semaphores directly instead of using epicsMutexes).
>
> I propose this change now, because we have fixed it now.
>
> Till needs to comment on details of the implementation...
At SLAC we had -- for years -- patched this old #undef. What I only noticed recently is that even if you patch the #undef EPICS still uses non-PI mutexes in some other places (e.g., the mutex protecting the condition variable in epicsEvent) and this is what this particular contribution addresses.
Re - 'root': it is absolutely correct that you don't have to be root to use SCHED_FIFO (and at SLAC we had followed the guidelines linked by Ralph). Using 'root' is just the easiest way to get there...
mdavidsaver (mdavidsaver) wrote : | # |
Another question, what motivates the addition of epicsPosixMutex*()? At first glance, I'm not enthused to add an OS specific extension to an OS independent API?
mdavidsaver (mdavidsaver) wrote : | # |
> ... still uses non-PI mutexes in some other places ...
Ah, I see. pthread_
mdavidsaver (mdavidsaver) wrote : | # |
> I tried the simply minded (early morning) test of removing the two #undef lines.
Turns out I have a quick answer for this. Some ~emulated environments don't fully support the PI futex() operations. (eg. rr-project.org) In these cases pthread_
It seems like the right approach is to probe, as is done in osdThread.c, to see if PI mutex is available.
Andrew Johnson (anj) wrote : | # |
Names beginning with epics are generally intended for public use, which the epicsPosixMutex stuff shouldn't be. If I've understood the code correctly you aren't replacing the pthread_mutex_t type but are providing a wrapper for pthread_
I've read the essay in the comments just below epicsMutexOsdSh
Is the new test program generic? It has some CPU-affinity stuff in it which is conditional on glibc; what happens on other OSs (Windows, MacOS, RTEMS-5, embedded Linux) that can also do SMP and don't use glibc?
I'm not sure I like the new environment variable or understand what effect not defining it will have. Is there a good reason to continue to support the previous mode if in practice it's broken? As long as we document the changes in behavior when SCHED_FIFO is available (I'm assuming nothing should change when it isn't) I'm not convinced we need the env switch, which would resolve Dirk's question about how to set an environment variable before running a test program.
However I wonder if it would be desirable to run this new test (and maybe some of the other thread-related test programs too) twice, once with the default scheduler and once under SCHED_FIFO?
Finally I think we would probably want to delay merging this until after Heinz' RTEMS-5 code has been pulled in, that might require some changes to this MR because he uses the Posix APIs in the new RTEMS port.
Dirk Zimoch (dirk.zimoch) wrote : | # |
> Also, what is the motivation to economize pthread_
> __SIZEOF_
It's simply not necessary that each mutex creates its own attribute.
From the pthread_
"After a mutex attributes object has been used to initialize one or more mutexes, any function affecting the attributes object (including destruction) shall not affect any previously initialized mutexes."
Thus the same attribute can be used for all mutexes and the mutex does not use it any more after creation. It is a waste of not only space but also time and error handling complexity to have (and keep!) one in each mutex.
mdavidsaver (mdavidsaver) wrote : | # |
> ... intended for public use, which the epicsPosixMutex stuff shouldn't be ...
Ok, as internal helpers, calls like these (with an ANJ compliant name) are perfectly reasonable. I would like to see them moved to a private header. Maybe 'os/posix/
> ... I'm not convinced we need the env switch ...
Likewise. We already have the build time $(USE_POSIX_
> ... I'm not sure how it helps future maintainers to understand this file ...
I'll go further. In-code comments should provide meaningful context for the code around them. This discussion would make a nice core-talk (or maybe blog) post, but is a distraction here.
> ... I think we would probably want to delay merging this until after Heinz' RTEMS-5 code has been pulled in ...
I don't like this. To my mind, we merge whichever is ready first, and the second needs to be reworked to resolve any conflicts. I think this gives a health incentive for promptness.
Dirk Zimoch (dirk.zimoch) wrote : | # |
> Also, what is the motivation to economize pthread_
Should I make a separate merge request that just handles the removal of per-mutex attributes?
This may allow to merge the cleanup before before agreeing on the new features.
mdavidsaver (mdavidsaver) wrote : | # |
I think a smaller diff would help move the process faster, as would more targeted changes.
Personally, I'm not sure I'll accept a global pthread_
As a general rule, I prefer locality for various reasons. Modern processors with deep caches generally reward memory locality. Mostly though, it is a case of readability. I find it easier to understand code which doesn't reference globals.
Andrew Johnson (anj) wrote : | # |
Ack on Michael's private header idea since I believe these definitions should only be needed when compiling the code in libCom itself, not by the client application.
Looking at the code again, there is only one call to epicsPosixMutex
Similarly there is only one call to epicsPosixMutex
Hmm, according to the online spec (and both manpages I just checked) passing a NULL attribute into pthread_
I'm also not terribly keen on adding our own enum for selecting the mutex type either, why not just use the mutex type constants PTHREAD_
We don't support Solaris any more (especially Solaris 8), so those comments can go too.
I withdraw my "after RTEMS-5" suggesting, if this becomes ready first it should get merged. It looks like the two won't conflict anyway though.
Dirk Zimoch (dirk.zimoch) wrote : | # |
I am taking the changes apart now...
Dirk Zimoch (dirk.zimoch) wrote : | # |
> Names beginning with epics are generally intended for public use, which the
> epicsPosixMutex stuff shouldn't be. If I've understood the code correctly you
> aren't replacing the pthread_mutex_t type but are providing a wrapper for
> pthread_
> caller to set mutex attributes on the new mutex. Any new publicly visible
> symbols that are meant to be internal to a libCom implementation should begin
> with "osd", so maybe osd_mutex_init()?
I think something with "posix" in the name would be good as it only affects posix mutexes?
But maybe Till had more in mind: An OSI interface to ask for recursive or (maybe cheaper) non-recursive mutexes, no matter how they are implemented and what the OS is. Could be semM vs semB in vxworks or whatever in Windows. Of course the header should not be "osd" in that case.
Is there a need to ask for (possibly cheaper) non-recursive mutexes? They are now only used in osdSpin.c (only if no posix spin locks are available), in osdEvent.c and osdThread.c. Everything else uses recursive mutexes.
>
> I've read the essay in the comments just below epicsMutexOsdSh
> not sure how it helps future maintainers to understand this file. The
> discussion there doesn't seem appropriate in source code comments.
I will remove the essay.
>
> Is the new test program generic? It has some CPU-affinity stuff in it which is
> conditional on glibc; what happens on other OSs (Windows, MacOS, RTEMS-5,
> embedded Linux) that can also do SMP and don't use glibc?
The change is in posix only, not Windows or RTEMS. The test is Linux only. Maybe it should be posix only as well. The CPU affinities are used to guarantee CPU starvarion of the high priority thread on multi-core systems as well. Otherwise the test may show a false success. Should be possible to run the test on all posix architectures.
Do we need a cpu affinity OSI API?
>
> I'm not sure I like the new environment variable or understand what effect not
> defining it will have. Is there a good reason to continue to support the
> previous mode if in practice it's broken? As long as we document the changes
> in behavior when SCHED_FIFO is available (I'm assuming nothing should change
> when it isn't) I'm not convinced we need the env switch, which would resolve
> Dirk's question about how to set an environment variable before running a test
> program.
The previous implementation is a bit "faster on average" because the mutexes are simpler. But "fast" only counts in RT context, and there "faster on average" basically means "wrong".
I can take the environment variable out. It was in so we can easily switch back and forth to see the difference. And as an anti change-panic device.
>
> However I wonder if it would be desirable to run this new test (and maybe some
> of the other thread-related test programs too) twice, once with the default
> scheduler and once under SCHED_FIFO?
You mean expected failure is success? How to program that?
- 9f08d6d... by Dirk Zimoch
-
removed the in-code essay
- 45856b0... by Dirk Zimoch
-
remove epicsMutexMustInit, instead return NULL on failure
- 5ee60ae... by Dirk Zimoch
-
remove epicsPosixMutex
AttrGet( ) enum EpicsPosixMutex Property - b4afc97... by Dirk Zimoch
-
rename epicsPosixMutexInit to osdPosixMutexInit
Andrew Johnson (anj) wrote : | # |
> I think something with "posix" in the name would be good as it only affects posix mutexes?
Whether a mutex is "posix" or not is a characteristic of the target being built for, not the kind of mutex being created. There are no posix mutexes for our Windows or VxWorks implementations so that makes no sense to me. I suggested using a more pthreads-style name osd_mutex_init() because the thing it returns is an actual pthread_mutex_t which gets passed into other pthread_*() routines. Passing something apparently created by a non-pthreads routine into a pthreads routine is a bit of a code smell, it looks wrong even though it isn't really.
> You mean expected failure is success? How to program that?
There are some problems with the test program; it always defines TEST_PLAN as 8, but the number of test results that will be reported will not be the same for all builds. There are several tests which won't report any result if HAVE_CPU_AFFINITY is undefined. This situation is why the unitTest API provides testSkip(), which lets a program declare that it can't run some number of tests in its current configuration or on this target. That is how running the same test with both schedulers would work, at run-time the code detects whether the tests should pass or not and calls testSkip() instead if not. Please don't try to adjust the test plan at runtime instead, it's much easier to read test code if a particular group of tests always generates the same number of results.
My suggestion of running the thread test programs a second time on Linux was really an idea for Michael and Ralph to consider: On the CI runners that give us sudo it ought to be possible to configure our account's maxrtprio hard limit somewhere in the job setup, then we would add linux-only scripts such as epicsThreadPrio
prlimit -r 80 ./epicsThreadPr
- b611d31... by Dirk Zimoch
-
moved osdPosixMutexInit to private header
- e41fa06... by Dirk Zimoch
-
remove check of environment variable EPICS_MUTEX_
USE_PRIORITY_ INHERITANCE - 20c0dba... by Dirk Zimoch
-
removed comment about outdated solaris version
Dirk Zimoch (dirk.zimoch) wrote : | # |
Is there any posix implementation which does not support PTHREAD_
I mean: do we still need the alternative implementation?
I have 12 different Linux architectures, some quite old, and none uses that compilation path.
If we do, should _XOPEN_SOURCE be the thing to be tested or rather #ifdef PTHREAD_
Dirk Zimoch (dirk.zimoch) wrote : | # |
> If we do, should _XOPEN_SOURCE be the thing to be tested or rather #ifdef
> PTHREAD_
I just see that PTHREAD_
Dirk Zimoch (dirk.zimoch) wrote : | # |
Found a problem in one of my earlier changes. Will changes that and force-push soon....
- f2bd8ec... by Dirk Zimoch
-
PTHREAD_
MUTEX_NORMAL may not be defined when Posix version is too old (before 1995) - 3c7596d... by Dirk Zimoch
-
removed unreachable code
- 7e1812e... by Dirk Zimoch
-
added missing macro in case USE_POSIX_
THREAD_ PRIORITY_ SCHEDULING= NO - 5703c77... by Dirk Zimoch
-
removed warning which interfered with tests
Dirk Zimoch (dirk.zimoch) wrote : | # |
Problem was not as big as I first thought. No force-push necessary.
Still the question remains: Sould we support _XOPEN_SOURCE < 500 ?
mdavidsaver (mdavidsaver) wrote : | # |
I'm inclined to say no. Of course our problem is how to decide.
Technically, I find references in 'man feature_
Practically, if the change passes the Linux and OSX CI builders, I'd be happy to accept the simplification.
mdavidsaver (mdavidsaver) wrote : | # |
Also, it may be worth repeating. Even on modern Linux, support for PTHREAD_
So I think it is necessary to probe for support at runtime. eg. with glibc, an error will be returned by pthread_
The obvious place to do this would be in your thread once function. Try to create a mutex with PTHREAD_
Dirk Zimoch (dirk.zimoch) wrote : | # |
Sure, run time failure needs to be considered. Falling back from PTHREAD_
What we really need is recursive mutexes. Removing the pre 1995 compatible code (older than VxWorks 5.5, yay) would simplify the implementation a lot. As long as we document the drop of support, I think we are fine.
- 4039d97... by Dirk Zimoch
-
drop workaround for missing PTHREAD_
MUTEX_RECURSIVE support
Dirk Zimoch (dirk.zimoch) wrote : | # |
I have now dropped the workaround for missing PTHREAD_
Falling back from PTHREAD_
Setting PTHREAD_
The only new external function is now called osdPosixMutexInit (not starting with "epics") and is in the private header file osdPosixMutexPriv.h which does not get installed. It is used inside libCom only.
I am not yet happy with the test. I need to find out how to use testSkip() properly when SCHED_FIFO is not available or the prority range is too limited.
There seems to be no TESTPROD_HOST_Posix (instead of TESTPROD_
- 4197954... by Dirk Zimoch
-
remove unnecessary setAttrDefaults() function
- 6d54127... by Dirk Zimoch
-
run epicsMutexPrior
ityInversionTes t for all Posix architectures
Andrew Johnson (anj) wrote : | # |
A couple more small changes that I noticed last week (see my inline comments), these may be the last changes we ask for – thanks!
- 9eda0c0... by Dirk Zimoch
-
removed useless epicsShare macros from osdPosixMutexInit
Dirk Zimoch (dirk.zimoch) wrote : | # |
Done. I would like to fix the test so that it does not complain when RT prios are not available....
- f003ed6... by Dirk Zimoch
-
Do not fail (but skip) epicsMutexPrior
ityInversionTes t when SCHED_FIFO is unavailable - 03b909b... by Dirk Zimoch
-
do not count as test that posix functions are actually working
- d5dbd83... by Dirk Zimoch
-
run epicsMutexPrior
ityInversionTes t even when CPU affinity is not available for old glibc because those systems likely have only one CPU core - a09e429... by Dirk Zimoch
-
relax epicsMutexPrior
ityInversionTes t timing a bit
Dirk Zimoch (dirk.zimoch) wrote : | # |
I think this is done.
mdavidsaver (mdavidsaver) wrote : | # |
I pushed your latest to github for testing. There are a couple of failures with RTEMS and OSX.
https:/
I still think it is necessary for the new globalAttrInit() to probe for runtime support of PTHREAD_
Unless I'm misreading, it looks like only the recursive implementation would have PTHREAD_
Heinz Junkes (junkes) wrote : | # |
Yes, even RTEMS5 without the new libbsd-
I saw the whole thing in the context of MotorRecords (OMS):
https:/
I see a general problem that probably in some places mutexes are created in the iocInit task and then used afterwards in other tasks although the iocInit task has been terminated.
As I understand it, mutexes should generally be created in the tasks in which they are also used? Or with shared memory the creating task must remain however existing?
Heinz Junkes (junkes) wrote : | # |
> Yes, even RTEMS5 without the new libbsd-
> _XOPEN_SOURCE and asks for the old implementation. I can set _XOPEN_SOURCE by
> hand and it works so far with it. But what I noticed in this context and I
> think that the mutexes may be used incorrectly in some modules and therefore
> it comes to problems such as: "mutex not owned by the task".
>
> I saw the whole thing in the context of MotorRecords (OMS):
>
> https:/
>
> I see a general problem that probably in some places mutexes are created in
> the iocInit task and then used afterwards in other tasks although the iocInit
> task has been terminated.
>
> As I understand it, mutexes should generally be created in the tasks in which
> they are also used? Or with shared memory the creating task must remain
> however existing?
If you build RTEMS with the POSIX_FLAGS like in Linux (-D_GNU_SOURCE -D_DEFAULT_SOURCE) then with the help of includes/
Dirk Zimoch (dirk.zimoch) wrote : | # |
Now I have some time again to return to this one...
Michael, if I understand correctly, you would like to see a run-time test that the attribute PTHREAD_
Heinz, do you think I should add #define D_GNU_SOURCE somewhere in the code or should that be done in the configuration?
Concerning "mutex not owned by task": As a mutex is used to synchronize different tasks, there is no specific task "owning" it permanently. The task that has last locked it owns it, independent of the creator task. The only situation where I would expect such a message is if task A has locked it but task B tries to unlock it. Or no task has locked it but now it is unlocked. That has nothing to do which who created it. The problem in the MotorRecord originated in the wrong check of TryLock and thus a confusion which task owned the mutex at run time.
- 30a2421... by Dirk Zimoch
-
check at run-time if PTHREAD_
PRIO_INHERIT actually works - b0f1271... by Dirk Zimoch
-
define _GNU_SOURCE for all platforms to make sure all needed posix functions are available
- 855519c... by Dirk Zimoch
-
No need to include features.h directly. And may fail on some OS.
Heinz Junkes (junkes) wrote : | # |
I added the following to RTEMS:
in configure/
POSIX_CPPFLAGS = -D_GNU_SOURCE -D_DEFAULT_SOURCE
Just like it is already done for Linux:
in configure/
# Define _GNU_SOURCE and _DEFAULT_SOURCE for maximum portability
POSIX_CPPFLAGS = -D_GNU_SOURCE -D_DEFAULT_SOURCE
> Now I have some time again to return to this one...
>
> Michael, if I understand correctly, you would like to see a run-time test that
> the attribute PTHREAD_
> you really seen that pthread_
> PTHREAD_
> used to init a mutex? If yes, I find that behavior strange and unexpected.
>
> Heinz, do you think I should add #define D_GNU_SOURCE somewhere in the code or
> should that be done in the configuration?
>
> Concerning "mutex not owned by task": As a mutex is used to synchronize
> different tasks, there is no specific task "owning" it permanently. The task
> that has last locked it owns it, independent of the creator task. The only
> situation where I would expect such a message is if task A has locked it but
> task B tries to unlock it. Or no task has locked it but now it is unlocked.
> That has nothing to do which who created it. The problem in the MotorRecord
> originated in the wrong check of TryLock and thus a confusion which task owned
> the mutex at run time.
mdavidsaver (mdavidsaver) wrote : | # |
> ... Have you really seen ...
Yes Dirk, really really. cf. https:/
If we open the source it's easy enough to see what glibc (at least) is doing.
So pthread_
And here is pthread_
If anything, I'm left with some gratitude that glibc does this test in pthread_
Also, seeing how pthread_mutexattr_t is only a bit mask makes me skeptical of economizing.
Dirk Zimoch (dirk.zimoch) wrote : | # |
I have now added the run-time test, removed the unnecessary direct include of features.h and added _GNU_SOURCE to the source code. I am now rebasing everything onto the current 7.0.
- b410ce8... by Dirk Zimoch
-
don't destroy mutex when init failed
mdavidsaver (mdavidsaver) wrote : | # |
I think this leaves only the question of osdPosixMutexInit() and PTHREAD_
wrt. _GNU_SOURCE I don't think this should be defined in osdMutex.c, or any source file. This is easily removed later though. Also, it is my turn to be surprised to see #ifdef _GNU_SOURCE appearing in RTEMS (which uses newlib, not glibc).
Dirk Zimoch (dirk.zimoch) wrote : | # |
> As it stands now, the mutexs created through epicsMutex can have PI enabled, but those created elsewhere (eg. osdSpin) will not.
Oops, you are right. I got confused by Andrew's earlier remark:
> Hmm, according to the online spec (and both manpages I just checked) passing a NULL attribute into pthread_
Of course, a non-recursive mutex with PI enabled is not a default mutex and we need a mutex attribute for those.
- 53fcc93... by Dirk Zimoch
-
fix bug in commit 5ee60a: We need a mutex attribute for non-recursive, PI enabled mutexes
Dirk Zimoch (dirk.zimoch) wrote : | # |
Concerning _GNU_SOURCE, this is what gnu.org has to say about this:
"You should define these macros by using ‘#define’ preprocessor directives at the top of your source code files. These directives must come before any #include of a system header file. It is best to make them the very first thing in the file, preceded only by comments. You could also use the ‘-D’ option to GCC, but it’s better if you make the source files indicate their own meaning in a self-contained way."
So better put it in the source files, not in the Makefile.
mdavidsaver (mdavidsaver) wrote : | # |
Ok, I think this covers all of the issues which I can see.
I've pushed your latest to my github to trigger CI builds, and a couple are failing with your favorite implicit declaration error.
https:/
> ../epicsMutexPr
I think that explicitly including pthread.h from epicsMutexPrior
Dirk Zimoch (dirk.zimoch) wrote : | # |
The failing MacOS compilation have this error:
../epicsMutex
#if defined(__GLIBC__) && defined(
It first tests that __GLIBC_PREREQ is defined and then caomplains that it is not defined?
Is it defined as a non-function-like macro on that platform?
The RTEMS compilations fail in the same line but with another error:
../epicsMutex
Again, it seems to have problems with __GLIBC_PREREQ.
The C Processor manual says that && and || "obey the usual short-circuiting rules of standard C." Thus __GLIBC_PREREQ(2,4) should never be evalueted if __GLIBC_PREREQ is not defined.
Anyway, I will try to fix this...
- a97e5af... by Dirk Zimoch
-
Do not rely on __GLIBC_PREREQ macro
Dirk Zimoch (dirk.zimoch) wrote : | # |
The CI environments should make sure to run the test with sufficient privileges to use RT priorities.
mdavidsaver (mdavidsaver) wrote : | # |
... __GLIBC_PREREQ
Ah, sorry I didn't catch this. There is a difference in how CPP treats undefined "variable" vs. "function" macros. eg. an undefined __GNUC__ will silently expand to zero. An undefined __GLIBC_PREREQ() is an error.
What you have is fine, and no further change is needed. So I think this change is now ready to merge.
I will note that this could also be expressed using the VERSION_INT() macro from epicsVersion.h as
> #ifdef __GLIBC__
> #define GLIBC_VERSION VERSION_
> #endif
and then
> #if GLIBC_VERSION >= VERSION_
This works since an undefined GLIBC_VERSION is treated as a zero.
> ... The CI environments should ...
This is currently being controlled by '.github/
> run: python .ci/cue.py test
Dirk Zimoch (dirk.zimoch) wrote : | # |
Ohhh, I still have a typo:
if __GLIBC__ * 100 * __GLIBC_MINOR__ >= 204
should be + __GLIBC_MINOR__ of course.
- b324abe... by Dirk Zimoch
-
fix typo in GLIBC version check
mdavidsaver (mdavidsaver) wrote : | # |
> ... I think this change is now ready to merge.
... once the CI builds pass.
https:/
RTEMS 4.10
../epicsMutex
Missing an include of <pthread.h>
OSX
../epicsMutex
Not sure about this one as <sched.h> is being included. Can someone with a Mac see if/where this is defined?
mdavidsaver (mdavidsaver) wrote : | # |
Ok, maybe time to take a(nother) step back and ask if epicsMutexPrior
> testOk1( hiPriStalledTimeUs < 1000 );
This will almost certainly fail randomly with the CI builders since they don't have a PREEMPT_RT kernel, and are anyway timeshare VMs.
I don't see a need for Base to test whether PTHREAD_
The question of OS priorities is answered by epicsThreadShow
Andrew Johnson (anj) wrote : | # |
OSX:
woz$ man sched_setscheduler
No manual entry for sched_setscheduler
This routine doesn't exist on macOS (Google confirms).
- 5ff7658... by Dirk Zimoch
-
add missing pthread.h
Dirk Zimoch (dirk.zimoch) wrote : | # |
Does that mean that OSX cannot use RT priorities?
- 786de29... by Dirk Zimoch
-
run epicsMutexPrior
ityInversionTes t only if we have priority scheduling enabled
Dirk Zimoch (dirk.zimoch) wrote : | # |
I have modified the Makefilt to run (even compile) the test only if priority scheduling is available. Otherwise the test would be rather pointless.
mdavidsaver (mdavidsaver) wrote : | # |
The OSX build still fails.
https:/
fyi. Base uses the pthread_
Dirk Zimoch (dirk.zimoch) wrote : | # |
Base also uses sched_setschedu
- 4855d9e... by Dirk Zimoch
-
use pthread_
setschedparam( ) instead of sched_setschedu ler() because of OSX
Andrew Johnson (anj) wrote : | # |
#define _POSIX_
mdavidsaver (mdavidsaver) wrote : | # |
https:/
Progress. Everything builds, but the new epicsMutexPrior
I think this change is basically done and ready to go except for the new test. Is this test so essential that it needs to be an obstacle to merging?
Maybe it would be sufficient for epicsMutexShowAll() to print a line showing if PTHREAD_
mdavidsaver (mdavidsaver) wrote : | # |
@Dirk, unless you object, I'm going to merge this work excluding epicsMutexPrior
Dirk Zimoch (dirk.zimoch) wrote : | # |
Feel free to remove the test.
mdavidsaver (mdavidsaver) wrote : | # |
I squashed the series and applied it as 5a8b6e4111b869e
Dirk Zimoch (dirk.zimoch) wrote : | # |
Unfortunately you introduced a bug in epicsMutexOsdSh
mdavidsaver (mdavidsaver) wrote : | # |
Corrected with 3c46542630823a2
Which targets don't define _POSIX_
Andrew Johnson (anj) wrote : | # |
There's also an issue that Brendan found and fixed in his rtems5 branch, the assumption in calls to osdPosixMutexInit() in osdEvent.c, osdSpin.c and osdThread.c that PTHREAD_
mdavidsaver (mdavidsaver) wrote : | # |
To expand, I take it you're referring to the change which replaces
> osdPosixMutexIn
calls with
> osdPosixMutexIn
Where RTEMS 5 has
> .../include/
This is a simple enough change, and the exact timetable for RTEMS5 support is uncertain enough to me, that I think it should be applied on 7.0 soon. Do you (ANJ) want to do this? If not, then I will.
Andrew Johnson (anj) wrote : | # |
Yup. I was assuming that no other target would need those changes before RTEMS-5 since our CI jobs haven't failed, although I guess freebsd might (could we add that to our GitHub-Actions jobs?). I'm a bit snowed under right now so if you think this needs to go in before the rest of RTEMS-5 go ahead and cherry-pick Brendan's change.
mdavidsaver (mdavidsaver) wrote : | # |
Looking at the other open libc impls. uclibc has almost the same enum{} as glibc, so PTHREAD_
> ... since our CI jobs haven't failed ...
This would depend on what 0 actually means.
> ... could we add that to our GitHub-Actions jobs?
Maybe? This will probably wait for someone with sufficient interest in epics on *BSD.
> ... go ahead and cherry-pick Brendan's change.
Ok. Simple as it may be, I'll wait for a CI run before pushing to the 7.0 branch.
https:/
Dirk Zimoch (dirk.zimoch) wrote : | # |
Michael, the target without _POSIX_
Dirk Zimoch (dirk.zimoch) wrote : | # |
I am now testing the PTHREAD_
Dirk Zimoch (dirk.zimoch) wrote : | # |
Compiles for all my targets.
mdavidsaver (mdavidsaver) wrote : | # |
Not directly related to Dirk's change, but I want to note it somewhere. The fallback implementation in posix/epicsAtom
Preview Diff
1 | diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md |
2 | index 1679526..1ef81a4 100644 |
3 | --- a/documentation/RELEASE_NOTES.md |
4 | +++ b/documentation/RELEASE_NOTES.md |
5 | @@ -17,6 +17,7 @@ should also be read to understand what has changed since earlier releases. |
6 | |
7 | <!-- Insert new items immediately below here ... --> |
8 | |
9 | +<<<<<<< documentation/RELEASE_NOTES.md |
10 | |
11 | |
12 | ### Build System: New `VALID_BUILDS` type "Command" |
13 | @@ -36,6 +37,14 @@ cross-compiled targets though since `CONFIG.Common.UnixCommon` sets it. |
14 | The other `VALID_BUILDS` types are "Host" for target architectures that can |
15 | compile and run their own programs (`PROD_HOST` etc.), and "Ioc" for targets |
16 | that can run IOCs (`PROD_IOC` etc.). |
17 | +======= |
18 | +### Priority inversion safe posix mutexes |
19 | + |
20 | +On Posix systems, epicsMutex now support priority inheritance if available. |
21 | +The IOC needs to run with SCHED_FIFO engaged. |
22 | +Support for Posix implementations before POSIX.1-2001 (_XOPEN_SOURCE < 500, |
23 | +glibc version < 2.3.3) has been dropped. |
24 | +>>>>>>> documentation/RELEASE_NOTES.md |
25 | |
26 | ### Support for JSON5 |
27 | |
28 | diff --git a/modules/libcom/src/osi/Makefile b/modules/libcom/src/osi/Makefile |
29 | index f0108e6..ce9dcc6 100644 |
30 | --- a/modules/libcom/src/osi/Makefile |
31 | +++ b/modules/libcom/src/osi/Makefile |
32 | @@ -112,6 +112,7 @@ Com_SRCS += osdStdio.c |
33 | THREAD_CPPFLAGS_NO += -DDONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING |
34 | osdThread_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING)) |
35 | osdSpin_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING)) |
36 | +osdMutex_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING)) |
37 | |
38 | Com_SRCS += osdThread.c |
39 | Com_SRCS += osdThreadExtra.c |
40 | diff --git a/modules/libcom/src/osi/os/posix/osdEvent.c b/modules/libcom/src/osi/os/posix/osdEvent.c |
41 | index 251f35d..db61c24 100644 |
42 | --- a/modules/libcom/src/osi/os/posix/osdEvent.c |
43 | +++ b/modules/libcom/src/osi/os/posix/osdEvent.c |
44 | @@ -23,6 +23,7 @@ |
45 | #include "epicsEvent.h" |
46 | #include "epicsTime.h" |
47 | #include "errlog.h" |
48 | +#include "osdPosixMutexPriv.h" |
49 | |
50 | struct epicsEventOSD { |
51 | pthread_mutex_t mutex; |
52 | @@ -50,11 +51,11 @@ LIBCOM_API epicsEventId epicsEventCreate(epicsEventInitialState init) |
53 | epicsEventId pevent = malloc(sizeof(*pevent)); |
54 | |
55 | if (pevent) { |
56 | - int status = pthread_mutex_init(&pevent->mutex, 0); |
57 | + int status = osdPosixMutexInit(&pevent->mutex, 0); |
58 | |
59 | pevent->isFull = (init == epicsEventFull); |
60 | if (status) { |
61 | - printStatus(status, "pthread_mutex_init", "epicsEventCreate"); |
62 | + printStatus(status, "osdPosixMutexInit", "epicsEventCreate"); |
63 | } else { |
64 | status = pthread_cond_init(&pevent->cond, 0); |
65 | if (!status) |
66 | diff --git a/modules/libcom/src/osi/os/posix/osdMutex.c b/modules/libcom/src/osi/os/posix/osdMutex.c |
67 | index 707ae63..6279c57 100644 |
68 | --- a/modules/libcom/src/osi/os/posix/osdMutex.c |
69 | +++ b/modules/libcom/src/osi/os/posix/osdMutex.c |
70 | @@ -11,6 +11,10 @@ |
71 | |
72 | /* Author: Marty Kraimer Date: 13AUG1999 */ |
73 | |
74 | +#ifndef _GNU_SOURCE |
75 | +#define _GNU_SOURCE |
76 | +#endif |
77 | + |
78 | #include <stddef.h> |
79 | #include <stdlib.h> |
80 | #include <stdio.h> |
81 | @@ -21,6 +25,7 @@ |
82 | #include <pthread.h> |
83 | |
84 | #include "epicsMutex.h" |
85 | +#include "osdPosixMutexPriv.h" |
86 | #include "cantProceed.h" |
87 | #include "epicsTime.h" |
88 | #include "errlog.h" |
89 | @@ -38,6 +43,69 @@ |
90 | cantProceed((method)); \ |
91 | } |
92 | |
93 | +#if defined(DONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING) |
94 | +#undef _POSIX_THREAD_PRIO_INHERIT |
95 | +#endif |
96 | + |
97 | +/* Global var - pthread_once does not support passing args but it is more efficient |
98 | + * than epicsThreadOnce which always acquires a mutex. |
99 | + */ |
100 | +static pthread_mutexattr_t globalAttrDefault; |
101 | +static pthread_mutexattr_t globalAttrRecursive; |
102 | +static pthread_once_t globalAttrInitOnce = PTHREAD_ONCE_INIT; |
103 | + |
104 | +static void globalAttrInit() |
105 | +{ |
106 | + int status; |
107 | + |
108 | + status = pthread_mutexattr_init(&globalAttrDefault); |
109 | + checkStatusQuit(status,"pthread_mutexattr_init(&globalAttrDefault)","globalAttrInit"); |
110 | + status = pthread_mutexattr_init(&globalAttrRecursive); |
111 | + checkStatusQuit(status,"pthread_mutexattr_init(&globalAttrRecursive)","globalAttrInit"); |
112 | + status = pthread_mutexattr_settype(&globalAttrRecursive, PTHREAD_MUTEX_RECURSIVE); |
113 | + checkStatusQuit(status, "pthread_mutexattr_settype(&globalAttrRecursive, PTHREAD_MUTEX_RECURSIVE)", "globalAttrInit"); |
114 | +#if defined _POSIX_THREAD_PRIO_INHERIT |
115 | + status = pthread_mutexattr_setprotocol(&globalAttrDefault, PTHREAD_PRIO_INHERIT); |
116 | + if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocol(&globalAttrDefault, PTHREAD_PRIO_INHERIT)"); |
117 | + status = pthread_mutexattr_setprotocol(&globalAttrRecursive, PTHREAD_PRIO_INHERIT); |
118 | + if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocol(&globalAttrRecursive, PTHREAD_PRIO_INHERIT)"); |
119 | + if (status == 0) { |
120 | + /* Can we really use PTHREAD_PRIO_INHERIT? */ |
121 | + pthread_mutex_t temp; |
122 | + status = pthread_mutex_init(&temp, &globalAttrRecursive); |
123 | + if (errVerbose) checkStatus(status, "pthread_mutex_init(&temp, &globalAttrRecursive)"); |
124 | + if (status != 0) { |
125 | + /* No, PTHREAD_PRIO_INHERIT does not work, fall back to PTHREAD_PRIO_NONE */; |
126 | + pthread_mutexattr_setprotocol(&globalAttrDefault, PTHREAD_PRIO_NONE); |
127 | + pthread_mutexattr_setprotocol(&globalAttrRecursive, PTHREAD_PRIO_NONE); |
128 | + } else { |
129 | + pthread_mutex_destroy(&temp); |
130 | + } |
131 | + } |
132 | +#endif |
133 | +} |
134 | + |
135 | +int osdPosixMutexInit (pthread_mutex_t *m, int mutextype) |
136 | +{ |
137 | + pthread_mutexattr_t *atts; |
138 | + int status; |
139 | + |
140 | + status = pthread_once( &globalAttrInitOnce, globalAttrInit ); |
141 | + checkStatusQuit(status,"pthread_once","epicsPosixMutexAttrGet"); |
142 | + |
143 | + switch (mutextype) { |
144 | + case PTHREAD_MUTEX_DEFAULT: |
145 | + atts = &globalAttrDefault; |
146 | + break; |
147 | + case PTHREAD_MUTEX_RECURSIVE: |
148 | + atts = &globalAttrRecursive; |
149 | + break; |
150 | + default: |
151 | + return ENOTSUP; |
152 | + } |
153 | + return pthread_mutex_init(m, atts); |
154 | +} |
155 | + |
156 | static int mutexLock(pthread_mutex_t *id) |
157 | { |
158 | int status; |
159 | @@ -48,23 +116,8 @@ static int mutexLock(pthread_mutex_t *id) |
160 | return status; |
161 | } |
162 | |
163 | -/* Until these can be demonstrated to work leave them undefined*/ |
164 | -/* On solaris 8 _POSIX_THREAD_PRIO_INHERIT fails*/ |
165 | -#undef _POSIX_THREAD_PROCESS_SHARED |
166 | -#undef _POSIX_THREAD_PRIO_INHERIT |
167 | - |
168 | -/* Two completely different implementations are provided below |
169 | - * If support is available for PTHREAD_MUTEX_RECURSIVE then |
170 | - * only pthread_mutex is used. |
171 | - * If support is not available for PTHREAD_MUTEX_RECURSIVE then |
172 | - * a much more complicated solution is required |
173 | - */ |
174 | - |
175 | - |
176 | |
177 | -#if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500 |
178 | typedef struct epicsMutexOSD { |
179 | pthread_mutex_t lock; |
180 | - pthread_mutexattr_t mutexAttr; |
181 | } epicsMutexOSD; |
182 | |
183 | epicsMutexOSD * epicsMutexOsdCreate(void) { |
184 | @@ -73,32 +126,12 @@ epicsMutexOSD * epicsMutexOsdCreate(void) { |
185 | |
186 | pmutex = calloc(1, sizeof(*pmutex)); |
187 | if(!pmutex) |
188 | - goto fail; |
189 | - |
190 | - status = pthread_mutexattr_init(&pmutex->mutexAttr); |
191 | - if (status) |
192 | - goto fail; |
193 | - |
194 | -#if defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0 |
195 | - status = pthread_mutexattr_setprotocol(&pmutex->mutexAttr, |
196 | - PTHREAD_PRIO_INHERIT); |
197 | - if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocal"); |
198 | -#endif /*_POSIX_THREAD_PRIO_INHERIT*/ |
199 | - |
200 | - status = pthread_mutexattr_settype(&pmutex->mutexAttr, |
201 | - PTHREAD_MUTEX_RECURSIVE); |
202 | - checkStatus(status, "pthread_mutexattr_settype"); |
203 | - if (status) |
204 | - goto fail; |
205 | + return NULL; |
206 | |
207 | - status = pthread_mutex_init(&pmutex->lock, &pmutex->mutexAttr); |
208 | - if (status) |
209 | - goto dattr; |
210 | - return pmutex; |
211 | + status = osdPosixMutexInit(&pmutex->lock, PTHREAD_MUTEX_RECURSIVE); |
212 | + if (!status) |
213 | + return pmutex; |
214 | |
215 | -dattr: |
216 | - pthread_mutexattr_destroy(&pmutex->mutexAttr); |
217 | -fail: |
218 | free(pmutex); |
219 | return NULL; |
220 | } |
221 | @@ -109,11 +142,9 @@ void epicsMutexOsdDestroy(struct epicsMutexOSD * pmutex) |
222 | |
223 | status = pthread_mutex_destroy(&pmutex->lock); |
224 | checkStatus(status, "pthread_mutex_destroy"); |
225 | - status = pthread_mutexattr_destroy(&pmutex->mutexAttr); |
226 | - checkStatus(status, "pthread_mutexattr_destroy"); |
227 | free(pmutex); |
228 | } |
229 | - |
230 | |
231 | + |
232 | void epicsMutexOsdUnlock(struct epicsMutexOSD * pmutex) |
233 | { |
234 | int status; |
235 | @@ -157,178 +188,3 @@ void epicsMutexOsdShow(struct epicsMutexOSD * pmutex, unsigned int level) |
236 | */ |
237 | printf(" pthread_mutex_t* uaddr=%p\n", &pmutex->lock); |
238 | } |
239 | - |
240 | |
241 | -#else /*defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500 */ |
242 | - |
243 | -typedef struct epicsMutexOSD { |
244 | - pthread_mutex_t lock; |
245 | - pthread_mutexattr_t mutexAttr; |
246 | - pthread_cond_t waitToBeOwner; |
247 | -#if defined(_POSIX_THREAD_PROCESS_SHARED) && _POSIX_THREAD_PROCESS_SHARED > 0 |
248 | - pthread_condattr_t condAttr; |
249 | -#endif /*_POSIX_THREAD_PROCESS_SHARED*/ |
250 | - int count; |
251 | - int owned; /* TRUE | FALSE */ |
252 | - pthread_t ownerTid; |
253 | -} epicsMutexOSD; |
254 | - |
255 | -epicsMutexOSD * epicsMutexOsdCreate(void) { |
256 | - epicsMutexOSD *pmutex; |
257 | - int status; |
258 | - |
259 | - pmutex = calloc(1, sizeof(*pmutex)); |
260 | - if(!pmutex) |
261 | - return NULL; |
262 | - |
263 | - status = pthread_mutexattr_init(&pmutex->mutexAttr); |
264 | - if(status) |
265 | - goto fail; |
266 | - |
267 | -#if defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0 |
268 | - status = pthread_mutexattr_setprotocol( |
269 | - &pmutex->mutexAttr,PTHREAD_PRIO_INHERIT); |
270 | - if (errVerbose) checkStatus(status, "pthread_mutexattr_setprotocal"); |
271 | -#endif /*_POSIX_THREAD_PRIO_INHERIT*/ |
272 | - |
273 | - status = pthread_mutex_init(&pmutex->lock, &pmutex->mutexAttr); |
274 | - if(status) |
275 | - goto dattr; |
276 | - |
277 | -#if defined(_POSIX_THREAD_PROCESS_SHARED) && _POSIX_THREAD_PROCESS_SHARED > 0 |
278 | - status = pthread_condattr_init(&pmutex->condAttr); |
279 | - checkStatus(status, "pthread_condattr_init"); |
280 | - status = pthread_condattr_setpshared(&pmutex->condAttr, |
281 | - PTHREAD_PROCESS_PRIVATE); |
282 | - checkStatus(status, "pthread_condattr_setpshared"); |
283 | - status = pthread_cond_init(&pmutex->waitToBeOwner, &pmutex->condAttr); |
284 | -#else |
285 | - status = pthread_cond_init(&pmutex->waitToBeOwner, 0); |
286 | -#endif /*_POSIX_THREAD_PROCESS_SHARED*/ |
287 | - if(status) |
288 | - goto dmutex; |
289 | - |
290 | - return pmutex; |
291 | - |
292 | -dmutex: |
293 | - pthread_mutex_destroy(&pmutex->lock); |
294 | -dattr: |
295 | - pthread_mutexattr_destroy(&pmutex->mutexAttr); |
296 | -fail: |
297 | - free(pmutex); |
298 | - return NULL; |
299 | -} |
300 | - |
301 | -void epicsMutexOsdDestroy(struct epicsMutexOSD * pmutex) |
302 | -{ |
303 | - int status; |
304 | - |
305 | - status = pthread_cond_destroy(&pmutex->waitToBeOwner); |
306 | - checkStatus(status, "pthread_cond_destroy"); |
307 | -#if defined(_POSIX_THREAD_PROCESS_SHARED) && _POSIX_THREAD_PROCESS_SHARED > 0 |
308 | - status = pthread_condattr_destroy(&pmutex->condAttr); |
309 | -#endif /*_POSIX_THREAD_PROCESS_SHARED*/ |
310 | - status = pthread_mutex_destroy(&pmutex->lock); |
311 | - checkStatus(status, "pthread_mutex_destroy"); |
312 | - status = pthread_mutexattr_destroy(&pmutex->mutexAttr); |
313 | - checkStatus(status, "pthread_mutexattr_destroy"); |
314 | - free(pmutex); |
315 | -} |
316 | - |
317 | |
318 | -void epicsMutexOsdUnlock(struct epicsMutexOSD * pmutex) |
319 | -{ |
320 | - int status; |
321 | - |
322 | - status = mutexLock(&pmutex->lock); |
323 | - checkStatus(status, "pthread_mutex_lock epicsMutexOsdUnlock"); |
324 | - if(status) |
325 | - return; |
326 | - |
327 | - if ((pmutex->count <= 0) || (pmutex->ownerTid != pthread_self())) { |
328 | - pthread_mutex_unlock(&pmutex->lock); |
329 | - checkStatus(status, "pthread_mutex_unlock epicsMutexOsdUnlock"); |
330 | - errlogPrintf("epicsMutexOsdUnlock but caller is not owner\n"); |
331 | - cantProceed("epicsMutexOsdUnlock but caller is not owner"); |
332 | - return; |
333 | - } |
334 | - |
335 | - pmutex->count--; |
336 | - if (pmutex->count == 0) { |
337 | - pmutex->owned = 0; |
338 | - pmutex->ownerTid = 0; |
339 | - status = pthread_cond_signal(&pmutex->waitToBeOwner); |
340 | - checkStatusQuit(status, "pthread_cond_signal epicsMutexOsdUnlock", "epicsMutexOsdUnlock"); |
341 | - } |
342 | - |
343 | - status = pthread_mutex_unlock(&pmutex->lock); |
344 | - checkStatus(status, "pthread_mutex_unlock epicsMutexOsdUnlock"); |
345 | -} |
346 | - |
347 | -static int condWait(pthread_cond_t *condId, pthread_mutex_t *mutexId) |
348 | -{ |
349 | - int status; |
350 | - |
351 | - while ((status = pthread_cond_wait(condId, mutexId)) == EINTR) { |
352 | - errlogPrintf("pthread_cond_wait returned EINTR. Violates SUSv3\n"); |
353 | - } |
354 | - return status; |
355 | -} |
356 | - |
357 | -epicsMutexLockStatus epicsMutexOsdLock(struct epicsMutexOSD * pmutex) |
358 | -{ |
359 | - pthread_t tid = pthread_self(); |
360 | - int status; |
361 | - |
362 | - if (!pmutex || !tid) return epicsMutexLockError; |
363 | - status = mutexLock(&pmutex->lock); |
364 | - if (status == EINVAL) return epicsMutexLockError; |
365 | - checkStatus(status, "pthread_mutex_lock epicsMutexOsdLock"); |
366 | - if(status) |
367 | - return epicsMutexLockError; |
368 | - |
369 | - while (pmutex->owned && !pthread_equal(pmutex->ownerTid, tid)) |
370 | - condWait(&pmutex->waitToBeOwner, &pmutex->lock); |
371 | - pmutex->ownerTid = tid; |
372 | - pmutex->owned = 1; |
373 | - pmutex->count++; |
374 | - |
375 | - status = pthread_mutex_unlock(&pmutex->lock); |
376 | - checkStatus(status, "pthread_mutex_unlock epicsMutexOsdLock"); |
377 | - if(status) |
378 | - return epicsMutexLockError; |
379 | - return epicsMutexLockOK; |
380 | -} |
381 | - |
382 | -epicsMutexLockStatus epicsMutexOsdTryLock(struct epicsMutexOSD * pmutex) |
383 | -{ |
384 | - pthread_t tid = pthread_self(); |
385 | - epicsMutexLockStatus result; |
386 | - int status; |
387 | - |
388 | - status = mutexLock(&pmutex->lock); |
389 | - if (status == EINVAL) return epicsMutexLockError; |
390 | - checkStatus(status, "pthread_mutex_lock epicsMutexOsdTryLock"); |
391 | - if(status) |
392 | - return epicsMutexLockError; |
393 | - |
394 | - if (!pmutex->owned || pthread_equal(pmutex->ownerTid, tid)) { |
395 | - pmutex->ownerTid = tid; |
396 | - pmutex->owned = 1; |
397 | - pmutex->count++; |
398 | - result = epicsMutexLockOK; |
399 | - } else { |
400 | - result = epicsMutexLockTimeout; |
401 | - } |
402 | - |
403 | - status = pthread_mutex_unlock(&pmutex->lock); |
404 | - checkStatus(status, "pthread_mutex_unlock epicsMutexOsdTryLock"); |
405 | - if(status) |
406 | - return epicsMutexLockError; |
407 | - return result; |
408 | -} |
409 | - |
410 | -void epicsMutexOsdShow(struct epicsMutexOSD *pmutex,unsigned int level) |
411 | -{ |
412 | - printf("ownerTid %p count %d owned %d\n", |
413 | - (void *)pmutex->ownerTid, pmutex->count, pmutex->owned); |
414 | -} |
415 | -#endif /*defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE)>=500 */ |
416 | diff --git a/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h b/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h |
417 | new file mode 100644 |
418 | index 0000000..2b6846c |
419 | --- /dev/null |
420 | +++ b/modules/libcom/src/osi/os/posix/osdPosixMutexPriv.h |
421 | @@ -0,0 +1,28 @@ |
422 | +/*************************************************************************\ |
423 | +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne |
424 | +* National Laboratory. |
425 | +* Copyright (c) 2002 The Regents of the University of California, as |
426 | +* Operator of Los Alamos National Laboratory. |
427 | +* SPDX-License-Identifier: EPICS |
428 | +* EPICS Base is distributed subject to a Software License Agreement found |
429 | +* in file LICENSE that is included with this distribution. |
430 | +\*************************************************************************/ |
431 | + |
432 | +#ifndef osdPosixMutexPrivh |
433 | +#define osdPosixMutexPrivh |
434 | + |
435 | +#include <pthread.h> |
436 | + |
437 | +#ifdef __cplusplus |
438 | +extern "C" { |
439 | +#endif |
440 | + |
441 | +/* Returns ENOTSUP if requested mutextype is not supported */ |
442 | +/* At the moment, only 0 (default non recursive mutex) and PTHREAD_MUTEX_RECURSIVE are supported */ |
443 | +int osdPosixMutexInit(pthread_mutex_t *,int mutextype); |
444 | + |
445 | +#ifdef __cplusplus |
446 | +} |
447 | +#endif |
448 | + |
449 | +#endif /* osdPosixMutexPrivh */ |
450 | diff --git a/modules/libcom/src/osi/os/posix/osdSpin.c b/modules/libcom/src/osi/os/posix/osdSpin.c |
451 | index 1f8434b..f039e7c 100644 |
452 | --- a/modules/libcom/src/osi/os/posix/osdSpin.c |
453 | +++ b/modules/libcom/src/osi/os/posix/osdSpin.c |
454 | @@ -109,6 +109,8 @@ void epicsSpinUnlock(epicsSpinId spin) { |
455 | * POSIX MUTEX IMPLEMENTATION |
456 | */ |
457 | |
458 | +#include <osdPosixMutexPriv.h> |
459 | + |
460 | typedef struct epicsSpin { |
461 | pthread_mutex_t lock; |
462 | } epicsSpin; |
463 | @@ -121,8 +123,8 @@ epicsSpinId epicsSpinCreate(void) { |
464 | if (!spin) |
465 | goto fail; |
466 | |
467 | - status = pthread_mutex_init(&spin->lock, NULL); |
468 | - checkStatus(status, "pthread_mutex_init"); |
469 | + status = osdPosixMutexInit(&spin->lock, 0); |
470 | + checkStatus(status, "osdPosixMutexInit"); |
471 | if (status) |
472 | goto fail; |
473 | |
474 | diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c |
475 | index fe375fd..4525588 100644 |
476 | --- a/modules/libcom/src/osi/os/posix/osdThread.c |
477 | +++ b/modules/libcom/src/osi/os/posix/osdThread.c |
478 | @@ -32,6 +32,7 @@ |
479 | #include "ellLib.h" |
480 | #include "epicsEvent.h" |
481 | #include "epicsMutex.h" |
482 | +#include "osdPosixMutexPriv.h" |
483 | #include "epicsString.h" |
484 | #include "epicsThread.h" |
485 | #include "cantProceed.h" |
486 | @@ -327,10 +328,10 @@ static void once(void) |
487 | int status; |
488 | |
489 | pthread_key_create(&getpthreadInfo,0); |
490 | - status = pthread_mutex_init(&onceLock,0); |
491 | - checkStatusOnceQuit(status,"pthread_mutex_init","epicsThreadInit"); |
492 | - status = pthread_mutex_init(&listLock,0); |
493 | - checkStatusOnceQuit(status,"pthread_mutex_init","epicsThreadInit"); |
494 | + status = osdPosixMutexInit(&onceLock,0); |
495 | + checkStatusOnceQuit(status,"osdPosixMutexInit","epicsThreadInit"); |
496 | + status = osdPosixMutexInit(&listLock,0); |
497 | + checkStatusOnceQuit(status,"osdPosixMutexInit","epicsThreadInit"); |
498 | pcommonAttr = calloc(1,sizeof(commonAttr)); |
499 | if(!pcommonAttr) checkStatusOnceQuit(errno,"calloc","epicsThreadInit"); |
500 | status = pthread_attr_init(&pcommonAttr->attr); |
501 | diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile |
502 | index 3ad869a..c49ac94 100755 |
503 | --- a/modules/libcom/test/Makefile |
504 | +++ b/modules/libcom/test/Makefile |
505 | @@ -333,6 +333,15 @@ TESTS += nonEpicsThreadPriorityTest |
506 | endif |
507 | endif |
508 | |
509 | +ifeq ($(POSIX),YES) |
510 | +ifeq ($(USE_POSIX_THREAD_PRIORITY_SCHEDULING),YES) |
511 | +TESTPROD_HOST += epicsMutexPriorityInversionTest |
512 | +epicsMutexPriorityInversionTest_SRCS += epicsMutexPriorityInversionTest.c |
513 | +epicsMutexPriorityInversionTest_SYS_LIBS_Linux += pthread rt |
514 | +TESTSCRIPTS_HOST+=epicsMutexPriorityInversionTest.t |
515 | +endif |
516 | +endif |
517 | + |
518 | include $(TOP)/configure/RULES |
519 | |
520 | rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl |
521 | diff --git a/modules/libcom/test/epicsMutexPriorityInversionTest.c b/modules/libcom/test/epicsMutexPriorityInversionTest.c |
522 | new file mode 100644 |
523 | index 0000000..8d7c6f2 |
524 | --- /dev/null |
525 | +++ b/modules/libcom/test/epicsMutexPriorityInversionTest.c |
526 | @@ -0,0 +1,258 @@ |
527 | +#ifndef _GNU_SOURCE |
528 | +#define _GNU_SOURCE |
529 | +#endif |
530 | +#include <sched.h> |
531 | +#include <epicsThread.h> |
532 | +#include <epicsEvent.h> |
533 | +#include <epicsMutex.h> |
534 | +#include <epicsUnitTest.h> |
535 | +#include <errlog.h> |
536 | +#include <testMain.h> |
537 | +#include <time.h> |
538 | +#include <envDefs.h> |
539 | +#include <stdio.h> |
540 | +#include <pthread.h> |
541 | + |
542 | +#define THE_CPU 0 |
543 | + |
544 | +#define SPIN_SECS 5000000 |
545 | + |
546 | +#if __GLIBC__ * 100 + __GLIBC_MINOR__ >= 204 |
547 | +#define HAVE_CPU_AFFINITY |
548 | +#else |
549 | +#undef HAVE_CPU_AFFINITY |
550 | +#endif |
551 | + |
552 | +typedef struct ThreadArg_ { |
553 | + epicsEventId sync; |
554 | + epicsEventId done; |
555 | + epicsMutexId mtx; |
556 | + volatile unsigned long end; |
557 | + unsigned long lim; |
558 | + int pri[3]; |
559 | +} ThreadArg; |
560 | + |
561 | +static unsigned long getClockUs() |
562 | +{ |
563 | + struct timespec now; |
564 | + if ( clock_gettime( CLOCK_MONOTONIC, &now ) ) { |
565 | + testAbort("clock_gettime(CLOCK_MONOTONIC) failed"); |
566 | + } |
567 | + return ((unsigned long)now.tv_sec)*1000000 + ((unsigned long)now.tv_nsec)/1000; |
568 | +} |
569 | + |
570 | +/* |
571 | + * Set affinity of executing thread to 'cpu' |
572 | + */ |
573 | +static void setThreadAffinity(int cpu) |
574 | +{ |
575 | +#ifdef HAVE_CPU_AFFINITY |
576 | + cpu_set_t cset; |
577 | + |
578 | + CPU_ZERO( &cset ); |
579 | + CPU_SET( cpu, &cset ); |
580 | + pthread_setaffinity_np( pthread_self(), sizeof(cset), &cset ); |
581 | +#endif |
582 | +} |
583 | + |
584 | +/* |
585 | + * Ensure only 'cpu' is set in executing thread's affinity mask |
586 | + */ |
587 | +static void checkAffinity(int cpu) |
588 | +{ |
589 | +#ifdef HAVE_CPU_AFFINITY |
590 | + cpu_set_t cset; |
591 | + |
592 | + if ( pthread_getaffinity_np( pthread_self(), sizeof(cset), &cset) ) { |
593 | + testDiag("pthread_getaffinity_np FAILED"); |
594 | + return; |
595 | + } |
596 | + if ( 1 != CPU_COUNT( &cset ) || ! CPU_ISSET( cpu, &cset ) ) { |
597 | + testDiag( "Thread has unexpected CPU affinity mask" ); |
598 | + } |
599 | +#endif |
600 | +} |
601 | + |
602 | + |
603 | +/* |
604 | + * Make sure executing thread uses SCHED_FIFO and |
605 | + * store its priority a->pri[idx] |
606 | + * |
607 | + * RETURNS: 0 if SCHED_FIFO engaged, nonzero otherwise. |
608 | + */ |
609 | +static int checkThreadPri(ThreadArg *a, unsigned idx) |
610 | +{ |
611 | + int pol; |
612 | + struct sched_param p; |
613 | + |
614 | + if ( pthread_getschedparam( pthread_self(), &pol, &p ) ) { |
615 | + testFail("pthread_getschedparam FAILED"); |
616 | + return 1; |
617 | + } |
618 | + if ( a && idx < sizeof(a->pri)/sizeof(a->pri[0]) ) { |
619 | + a->pri[idx] = p.sched_priority; |
620 | + } |
621 | + if ( SCHED_FIFO != pol ) { |
622 | + testDiag( "Thread uses unexpected scheduler" ); |
623 | + } |
624 | + return (SCHED_FIFO != pol); |
625 | +} |
626 | + |
627 | + |
628 | +/* |
629 | + * Low-priority thread. |
630 | + * |
631 | + * Lock mutex and signal to the medium-priority thread |
632 | + * that it may proceed. |
633 | + * |
634 | + * Since all three (high-, medium- and low-priority threads) |
635 | + * execute on the same CPU (via affinity) the medium- |
636 | + * priority thread will then take over the CPU and prevent |
637 | + * (unless priority-inheritance is enabled, that is) |
638 | + * the low-priority thread from continueing on to unlock |
639 | + * the mutex. |
640 | + */ |
641 | +static void loPriThread(void *parm) |
642 | +{ |
643 | + ThreadArg *a = (ThreadArg*)parm; |
644 | + |
645 | + checkAffinity( THE_CPU ); |
646 | + |
647 | + checkThreadPri( a, 0 ); |
648 | + |
649 | + epicsMutexMustLock( a->mtx ); |
650 | + |
651 | + epicsEventSignal( a->sync ); |
652 | + |
653 | + /* medium-priority thread takes over CPU and spins |
654 | + * while the high-priority thread waits for the mutex. |
655 | + * With priority-inheritance enabled this thread's |
656 | + * priority will be increased so that the medium- |
657 | + * priority thread can be preempted and we proceed |
658 | + * to release the mutex. |
659 | + */ |
660 | + |
661 | + epicsMutexUnlock( a->mtx ); |
662 | +} |
663 | + |
664 | +static void hiPriThread(void *parm) |
665 | +{ |
666 | + ThreadArg *a = (ThreadArg*)parm; |
667 | + |
668 | + /* Try to get the mutex */ |
669 | + epicsMutexMustLock( a->mtx ); |
670 | + /* Record the time when we obtained the mutex */ |
671 | + a->end = getClockUs(); |
672 | + epicsMutexUnlock( a->mtx ); |
673 | + /* Tell the main thread that the test done */ |
674 | + epicsEventSignal( a->done ); |
675 | +} |
676 | + |
677 | +static void miPriThread(void *parm) |
678 | +{ |
679 | + ThreadArg *a = (ThreadArg*)parm; |
680 | + |
681 | + /* Basic checks: |
682 | + * - affinity must be set to use single CPU for all threads |
683 | + * - SCHED_FIFO must be in effect |
684 | + */ |
685 | + checkAffinity( THE_CPU ); |
686 | + checkThreadPri( a, 1 ); |
687 | + |
688 | + /* Create the low-priority thread. */ |
689 | + epicsThreadMustCreate("testLo", |
690 | + a->pri[0], |
691 | + epicsThreadGetStackSize( epicsThreadStackMedium ), |
692 | + loPriThread, |
693 | + a); |
694 | + |
695 | + /* Wait until low-priority thread has taken the mutex */ |
696 | + epicsEventMustWait( a->sync ); |
697 | + |
698 | + /* Compute the end-time for our spinning loop */ |
699 | + a->lim = getClockUs() + SPIN_SECS; |
700 | + a->end = 0; |
701 | + |
702 | + /* Create the high-priority thread. The hiPri thread will |
703 | + * block for the mutex and |
704 | + * if priority-inheritance is available: |
705 | + * increase the low-priority thread's priority temporarily |
706 | + * so it can proceed to release the mutex and hand it to |
707 | + * the high-priority thread. |
708 | + * if priority-inheritance is not available: |
709 | + * the high-priority thread will have to wait until we are |
710 | + * done spinning and the low-priority thread is scheduled |
711 | + * again. |
712 | + */ |
713 | + epicsThreadMustCreate("testHi", |
714 | + a->pri[2], |
715 | + epicsThreadGetStackSize( epicsThreadStackMedium ), |
716 | + hiPriThread, |
717 | + a); |
718 | + |
719 | + /* Spin for some time; the goal is hogging the CPU and thus preventing |
720 | + * the low-priority thread from being scheduled. |
721 | + * If priority-inheritance is enabled then the low-priority thread's |
722 | + * priority is temporarily increased so that we can be preempted. This |
723 | + * then causes the high-priority thread to record the 'end' time which |
724 | + * tells us that we can terminate early... |
725 | + */ |
726 | + while ( 0 == a->end && getClockUs() < a->lim ) |
727 | + /* spin */; |
728 | + |
729 | + /* w/o priority-inheritance the low-priority thread may proceed at |
730 | + * this point and release the mutex to the high-priority thread. |
731 | + */ |
732 | +} |
733 | + |
734 | + |
735 | +#define NUM_TESTS 2 |
736 | + |
737 | +MAIN(epicsMutexPriorityInversionTest) |
738 | +{ |
739 | + ThreadArg a; |
740 | + long hiPriStalledTimeUs; |
741 | + struct sched_param p_pri; |
742 | + |
743 | + a.mtx = epicsMutexMustCreate(); |
744 | + a.sync = epicsEventMustCreate( epicsEventEmpty ); |
745 | + a.done = epicsEventMustCreate( epicsEventEmpty ); |
746 | + a.pri[0] = epicsThreadPriorityLow; |
747 | + a.pri[1] = epicsThreadPriorityMedium; |
748 | + a.pri[2] = epicsThreadPriorityHigh; |
749 | + |
750 | + testPlan(NUM_TESTS); |
751 | + |
752 | + p_pri.sched_priority = sched_get_priority_min( SCHED_FIFO ); |
753 | + if ( pthread_setschedparam(pthread_self(), SCHED_FIFO, &p_pri) ) { |
754 | + testDiag("SCHED_FIFO not engaged - cannot run this test"); |
755 | + testSkip( NUM_TESTS, "SCHED_FIFO not engaged" ); |
756 | + return testDone(); |
757 | + } else { |
758 | + testDiag("SCHED_FIFO is in use"); |
759 | + } |
760 | + |
761 | + setThreadAffinity( THE_CPU ); |
762 | + /* created threads should inherit CPU affinity mask */ |
763 | + |
764 | + epicsThreadMustCreate("testMi", |
765 | + a.pri[1], |
766 | + epicsThreadGetStackSize( epicsThreadStackMedium ), |
767 | + miPriThread, |
768 | + &a); |
769 | + |
770 | + epicsEventMustWait( a.done ); |
771 | + |
772 | + testOk1( (a.pri[0] < a.pri[1]) && (a.pri[1] < a.pri[2]) ); |
773 | + |
774 | + hiPriStalledTimeUs = a.end - (a.lim - SPIN_SECS); |
775 | + |
776 | + testDiag("High-priority thread stalled for %li us\n", hiPriStalledTimeUs); |
777 | + testOk1( hiPriStalledTimeUs < 1000 ); |
778 | + |
779 | + epicsEventDestroy( a.done ); |
780 | + epicsEventDestroy( a.sync ); |
781 | + epicsMutexDestroy( a.mtx ); |
782 | + |
783 | + return testDone(); |
784 | +} |
Test does not succeed when not running as root or EPICS_MUTEX_ USE_PRIORITY_ INHERITANCE is not set before.