Merge lp:~epics-core/epics-base/spinlockfix into lp:~epics-core/epics-base/3.15

Proposed by mdavidsaver
Status: Merged
Merged at revision: 12509
Proposed branch: lp:~epics-core/epics-base/spinlockfix
Merge into: lp:~epics-core/epics-base/3.15
Diff against target: 794 lines (+289/-77)
12 files modified
documentation/RELEASE_NOTES.html (+7/-6)
src/libCom/osi/Makefile (+1/-0)
src/libCom/osi/epicsAtomicDefault.h (+33/-13)
src/libCom/osi/epicsSpin.h (+2/-1)
src/libCom/osi/os/RTEMS/osdSpin.c (+53/-7)
src/libCom/osi/os/default/osdSpin.c (+9/-1)
src/libCom/osi/os/posix/epicsAtomicOSD.h (+1/-1)
src/libCom/osi/os/posix/osdSpin.c (+32/-7)
src/libCom/osi/os/solaris/epicsAtomicOSD.h (+1/-1)
src/libCom/osi/os/vxWorks/osdSpin.c (+51/-6)
src/libCom/test/epicsRunLibComTests.c (+3/-0)
src/libCom/test/epicsSpinTest.c (+96/-34)
To merge this branch: bzr merge lp:~epics-core/epics-base/spinlockfix
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
mdavidsaver Needs Resubmitting
Review via email: mp+220845@code.launchpad.net

Description of the change

To ensure proper OS independent behavior for spinlocks, disable task preemption on RTOS.

RTEMS/vxWorks implementations already disable interrupts.
However, if while holding a spinlock, a function call is made which wakes up a higher priority task,
a context switch will occur while the spinlock remains locked.
Thankfully the context switch will re-enable interrupts.
However, since epicsSpinLock for these OSs only disables interrupts,
the second task (or any other which happens to run) can successfuly
enter the critical seciton.

This branch changes epicsSpinLock() to also disable task preemption
on RTEMS/vxWorks. Further an assertion is added as a last line of
defence against silent violation of mutual exclusion.

Also, the POSIX implementation of spinlocks is vulnerable to priority inversion, so use the mutex based implementation with thread priorities are enabled.

Add epicsSpinMustCreate().

To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote :

Ping!

Revision history for this message
Andrew Johnson (anj) wrote :

In the RTEMS and VxWorks implementations of epicsSpinLock() and epicsSpinUnlock(), is it wise to make an assertion with both interrupts and thread dispatch disabled? Won't that result in a silent lock-up of the IOC if the assertion fails? Not helpful in the presence of a nasty bug that sprays memory, or of a bad memory chip.

The cantProceed() messages in the Posix implementation seem a little minimalistic, a little more detail might be helpful (put yourself in the position of an IOC developer who gets one of these, how useful are these to finding out what happened).

- Andrew

Revision history for this message
Eric Norum (wenorum) wrote :

I’m not a fan of assertions. Either a check is important or it isn’t. Not something to come and go between debugging and production versions.

On Jul 21, 2014, at 2:09 PM, Andrew Johnson <email address hidden> wr

> In the RTEMS and VxWorks implementations of epicsSpinLock() and epicsSpinUnlock(), is it wise to make an assertion with both interrupts and thread dispatch disabled? Won't that result in a silent lock-up of the IOC if the assertion fails? Not helpful in the presence of a nasty bug that sprays memory, or of a bad memory chip.
>
> The cantProceed() messages in the Posix implementation seem a little minimalistic, a little more detail might be helpful (put yourself in the position of an IOC developer who gets one of these, how useful are these to finding out what happened).
>
> - Andrew
> --
> https://code.launchpad.net/~epics-core/epics-base/spinlockfix/+merge/220845
> Your team EPICS Core Developers is requested to review the proposed merge of lp:~epics-core/epics-base/spinlockfix into lp:epics-base.

--
Eric Norum
<email address hidden>

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Would it be satisify you guys if "assert(X)" became "if(!X) cantProceed(Y)" where Y would be a message indicating that the some code slept while holding a spinlock?

> The cantProceed() messages in the Posix implementation ...

Do you mean the case where pthread_spin_lock() fails? The checkStatus() macro will already print strerror().

Revision history for this message
Andrew Johnson (anj) wrote :

As long as you're not going to be calling cantProceed() with interrupts and thread dispatch disabled that would be fine by me.

Thinking aloud, the error messages on a failure currently look something like this:

epicsSpin pthread_spin_lock failed: error <strerror>
epicsSpinLock pspin
Thread <name> (<id>) can't proceed, suspending.

epicsSpin pthread_mutex_lock failed: error <strerror>
epicsSpinLock pmutex
Thread <name> (<id>) can't proceed, suspending.

The middle line is actually redundant since the first line already tells us which implementation the code was built with. I would just pass NULL into the cantProceed() call instead.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> As long as you're not going to be calling cantProceed() with interrupts and
> thread dispatch disabled that would be fine by me.

Hanging in an ISR isn't nice, but neither is entering the critical section w/o the lock. We (or rather our users) will lose either way.

Which way should we lose?

> ...I would just pass NULL into the cantProceed() call instead.

Ok.

Revision history for this message
Andrew Johnson (anj) wrote :

I don't think you need to lose, you're going to be suspending this thread if you detect a problem so the user's code will only enter the critical section if they manually resume the thread. You should just unlock everything before you call cantProceed().

Ah, but if we're in interrupt context then we do have a problem, it's illegal to call cantProceed() in that context, and we don't have a status return value that we can use to tell the caller that the lock operation failed. Maybe we shouldn't even bother with the locked member and trying to detect this condition at all?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> ... Maybe we
> shouldn't even bother with the locked member and trying to detect this
> condition at all?

Something is required. Silently violating mutual exclusion is very difficult to find (ISRs are enough fun as it is).

For the present I'll change this to print a warning instead of cantProceed() in the ISR case.

12483. By mdavidsaver

epicsSpin: better error messages when mis-use is detected

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I've replace the usage of assert() with explicit error messages. In the ISR case it returns, otherwise cantProceed() is called. In both cases interrupts and preemption are re-enabled first.

12484. By mdavidsaver

epicsSpin: remove redundant cantProceed() messages

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> > ...I would just pass NULL into the cantProceed() call instead.
>
> Ok.

This is done.

review: Needs Resubmitting
12485. By Andrew Johnson

Final spinlock tidying-up

* Abort epicsSpinTest() if epicsSpinCreate() returns NULL
* Adjust RELEASE_NOTES that describe the implementations.

Revision history for this message
Andrew Johnson (anj) wrote :

I just committed a couple of changes to epicsSpinTest.c that abort if epicsSpinCreate() returns NULL, and added one use of epicsSpinMustCreate(). I also adjusted the wording in the Release Notes describing the different implementations.

There doesn't seem to be any documentation for the epicsSpin API in the AppDevGuide at all. Not sure whose omission that is, possibly Ralph's?

Then I tried running epicsSpinTest on vxWorks-68040:

5.5.2> epicsSpinTest
1..18
ok 1 - epicsSpinTryLock(verify.spin) == 0
Deadlock in epicsSpinLock(). Either recursive lock, missing unlock, or locked by sleeping thread.Thread verifyTryLockThread (0x1ecda84) can't proceed, suspending.

Huh? Tried the same on an unmodified Base, I get one test failing:

5.5.2> epicsSpinTest
1..18
ok 1 - epicsSpinTryLock(verify.spin) == 0
not ok 2 - epicsSpinTryLock(pVerify->spin)
ok 3 - epicsEventWait(verify.done) == epicsEventWaitOK
# spinThread 0 starting
ok 4 - spinThread 0 epicsSpinLock taken
...
[Remaining tests all passed, although the performance test measured a time of 0.000000 microseconds]

The verifyTryLock() test as written doesn't seem to even be valid any more. It is creating and taking a spinLock in the first testOk1(), then with the lock still held it creates a new thread and tries to take the lock in that thread, and expects that try to fail. However you were probably expecting that the new thread shouldn't even be able to start since you called taskLock(), but the man-page for taskLock() says this:

    The task that calls this routine will be the only task that
    is allowed to execute, unless the task explicitly gives up
    the CPU by making itself no longer ready.

The main thread called epicsEventWait(verify.done) after creating that new thread, so it made itself no longer ready thus allowing the VxWorks kernel to start task switching again.

The new thread then calls epicsSpinTryLock() which in the UP implementations isn't expecting to be able to run while the lock is held so it doesn't even try to implement a proper tryLock() operation, and it always returned OK before. However now it notices that the lock had already been taken, so it suspends.

I modified the test code to create the new thread before taking the lock and wait on an epicsEvent before trying to take the lock, but it made no difference to the result; the taskLock() still gets broken as soon as the main thread suspends.

Is this just a problem in our test code, or do we need to change our implementation to allow for the taskLock() rule? Does RTEMS have an equivalent rule for tasks that have called _Thread_Disable_dispatch()?

review: Needs Fixing
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> The main thread called epicsEventWait(verify.done) after creating that new thread, so it made itself no longer ready thus allowing the VxWorks kernel to start task switching again.

This seems like a clear violation of the "don't sleep while holding a
spinlock" rule. Which is what the "Deadlock in epicsSpinLock()" test is
designed to catch.

Revision history for this message
Andrew Johnson (anj) wrote :

Agreed, so the current test code is invalid, but I still don't think the UP implementations of epicsSpinTryLock() should ever be able to call cantProceed() even in this circumstance — the point of providing the TryLock() versions is so the user can write code that they know will never block.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> ... I still don't think the UP
> implementations of epicsSpinTryLock() should ever be able to call
> cantProceed() even in this circumstance ...

On UP RTOSs, the error in question can be triggered by three conditions, which can't be distinguished ATM.

1. Attempt at recursive locking
2. User code forgot epicsSpinUnlock()
3. User code slept while holding the spinlock

The only one which I imagine epicsSpinTryLock() handling sanely is #1, which would require that the epicsSpinId structure track the current thread ID with a special case for an ISR.

The other two are clear mis-uses of the API which are unrecoverable (#2 is a deadlock, #3 violates mutual exclusion).

So if good behavior is out the window, I'd prefer to fail early and visibly.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> ... I still don't think the UP
> implementations of epicsSpinTryLock() should ever be able to call
> cantProceed() even in this circumstance ...

Ok, so I was thinking about this from the context of epicsSpinLock(). The try lock does have an error path which can be used. I would argue that we shouldn't call this defined behavior as I don't think we want the overhead of detecting recursive calls in other implementations.

Revision history for this message
Andrew Johnson (anj) wrote :

If epicsSpinLock() would block [for any reason], epicsSpinTryLock() should immediately return a non-zero value in the same situation (EBUSY/EWOULDBLOCK and maybe even EDEADLOCK if we can detect that, I don't particularly like our current 1 return value for "busy").

We don't have to promise to detect recursion and deadlocks (although we should if we can easily do so), but I do maintain that the TryLock() call should never wait or block.

12486. By mdavidsaver

epicsSpinTest: fix verifyTryLock()

avoid sleeping with a spinlock held.
Now test only works on SMP systems.

12487. By mdavidsaver

epicsSpin: try lock return non-blocking

Avoid cantProceed() in try lock, even for undefined behavior.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> If epicsSpinLock() would block [for any reason], epicsSpinTryLock() should
> immediately return a non-zero value

Done.

I also fixed the trylock test for SMP systesm, and skip it for UP systems.

12488. By Andrew Johnson

Fix epicsAtomic headers when used from C code

Several C++ and C99-isms crept in.

12489. By Andrew Johnson

Fix epicsSpinTest.c spinThread tests

Runs many more rounds, without blocking with the lock held.

Revision history for this message
Andrew Johnson (anj) wrote :

Did you try building for RTEMS? My epicsSpinTest.c build failed on both
VxWorks and RTEMS due to problems with the epicsAtomic.h headers not
having been tested from C code before; they had a couple of C++isms that
I just fixed and committed.

However we still have a problem. Here's the output from epicsSpinTest on
VxWorks 5.5.2 from a 68040:

5.5.2> epicsSpinTest
1..16
ok 1 # SKIP verifyTryLock() only for SMP systems
# spinThread 0 starting
ok 2 - spinThread 0 epicsSpinLock taken
# spinThread 1 starting
# spinThread 2 starting
Deadlock in epicsSpinLock(). Either recursive lock, missing unlock, or
locked by sleeping thread.Thread task1 (0x1eccc48) can't proceed,
suspending.
Deadlock in epicsSpinLock(). Either recursive lock, missing unlock, or
locked by sleeping thread.Thread task2 (0x1ecba4c) can't proceed,
suspending.
ok 3 - spinThread 0 epicsSpinLock taken
ok 4 - spinThread 0 epicsSpinLock taken
ok 5 - spinThread 0 epicsSpinLock taken
ok 6 - spinThread 0 epicsSpinLock taken
# spinThread 0 exiting
# lock()*1/unlock()*1 takes 4.416667 microseconds

Planned 16 tests but only ran 6

    Results
    =======
       Tests: 6
      Passed: 6 = 100.00%
     Skipped: 1 = 16.67%
value = 2 = 0x2

Both task1 and task2 still exist and are suspended at the end of the
test. Same results on VxWorks 6.9 from a PowerPC (but with better lock
performance, 0.116667 microseconds).

Looks at the code...

void spinThread(void *arg)
{
    info *pinfo = (info *) arg;
    testDiag("spinThread %d starting", pinfo->threadnum);
    while (pinfo->quit--) {
        epicsSpinLock(pinfo->spin);
        testPass("spinThread %d epicsSpinLock taken", pinfo->threadnum);
        epicsThreadSleep(.1);

Lookey-there, not one but two blocking calls made while owning the
spinLock...

I reworked that spinThread test to add a shared counter variable
protected by the lock; to not output on every operation, increased the
number of rounds, and to signal the main thread when finished so it
doesn't have to guess (which it gets wrong). Oh, and it releases memory
as well just to be nice.

Please check my committed result.

- Andrew
--
Advertising may be described as the science of arresting the human
intelligence long enough to get money from it. -- Stephen Leacock

12490. By mdavidsaver

epicsSpinTest: plug some leaks

12491. By mdavidsaver

epicsSpinTest: add to libCom test harness

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Now passes on RTEMS 4.9. Fixed remaining memory leaks in epicsSpinTest and added it to the libCom test harness (was in the Makefile, but not being run)

> ***** epicsSpinTest *****
> 1..2
> ok 1 # SKIP verifyTryLock() only for SMP systems
> # spinThread 0 starting
> # spinThread 1 starting
> # spinThread 2 starting
> # spinThread 0 exiting
> # spinThread 1 exiting
> # spinThread 2 exiting
> ok 2 - Loops run = 1500 (expecting 1500)
> # lock()*1/unlock()*1 takes 0.240219 microseconds
>
> Results
> =======
> Tests: 2
> Passed: 2 = 100.00%
> Skipped: 1 = 50.00%

Revision history for this message
Andrew Johnson (anj) wrote :

Passes on VxWorks, merging.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'documentation/RELEASE_NOTES.html'
2--- documentation/RELEASE_NOTES.html 2014-06-12 19:47:42 +0000
3+++ documentation/RELEASE_NOTES.html 2014-07-25 22:18:29 +0000
4@@ -136,12 +136,13 @@
5 <p>The new header file epicsSpin.h adds a portable spin-locks API which is
6 intended for locking very short sections of code (typically one or two lines of
7 C or C++) to provide a critical section that protects against race conditions.
8-On Posix platforms this uses the pthread_spinlock_t type if it's available but
9-falls back to a pthread_mutex_t if not; on the UP VxWorks and RTEMS platforms
10-the implementations lock out the CPU interrupts; the default implementation
11-(used where no better implementation is available for the platform) uses an
12-epicsMutex. Spin-locks may not be taken recursively, and the code inside the
13-critical section should always be short and deterministic.</p>
14+On Posix platforms this uses the pthread_spinlock_t type if it's available and
15+the build is not configured to use Posix thread priorities, but otherwise it
16+falls back to a pthread_mutex_t. On the UP VxWorks and RTEMS platforms the
17+implementations lock out CPU interrupts and disable task preemption while a
18+spin-lock is held. The default implementation (used when no other implementation
19+is provided) uses an epicsMutex. Spin-locks may not be taken recursively, and
20+the code inside the critical section should be short and deterministic.</p>
21
22 <h3>Improvements to aToIPAddr()</h3>
23
24
25=== modified file 'src/libCom/osi/Makefile'
26--- src/libCom/osi/Makefile 2012-10-29 19:25:23 +0000
27+++ src/libCom/osi/Makefile 2014-07-25 22:18:29 +0000
28@@ -103,6 +103,7 @@
29 #POSIX thread priority scheduling flag
30 THREAD_CPPFLAGS_NO += -DDONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING
31 osdThread_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING))
32+osdSpin_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING))
33
34 Com_SRCS += osdThread.c
35 Com_SRCS += osdThreadExtra.c
36
37=== modified file 'src/libCom/osi/epicsAtomicDefault.h'
38--- src/libCom/osi/epicsAtomicDefault.h 2011-09-01 17:25:53 +0000
39+++ src/libCom/osi/epicsAtomicDefault.h 2014-07-25 22:18:29 +0000
40@@ -34,9 +34,11 @@
41 #ifndef EPICS_ATOMIC_INCR_INTT
42 EPICS_ATOMIC_INLINE int epicsAtomicIncrIntT ( int * pTarget )
43 {
44- EpicsAtomicLockKey key;
45+ EpicsAtomicLockKey key;
46+ int result;
47+
48 epicsAtomicLock ( & key );
49- const int result = ++(*pTarget);
50+ result = ++(*pTarget);
51 epicsAtomicUnlock ( & key );
52 return result;
53 }
54@@ -46,8 +48,10 @@
55 EPICS_ATOMIC_INLINE size_t epicsAtomicIncrSizeT ( size_t * pTarget )
56 {
57 EpicsAtomicLockKey key;
58+ size_t result;
59+
60 epicsAtomicLock ( & key );
61- const size_t result = ++(*pTarget);
62+ result = ++(*pTarget);
63 epicsAtomicUnlock ( & key );
64 return result;
65 }
66@@ -60,8 +64,10 @@
67 EPICS_ATOMIC_INLINE int epicsAtomicDecrIntT ( int * pTarget )
68 {
69 EpicsAtomicLockKey key;
70+ int result;
71+
72 epicsAtomicLock ( & key );
73- const int result = --(*pTarget);
74+ result = --(*pTarget);
75 epicsAtomicUnlock ( & key );
76 return result;
77 }
78@@ -71,8 +77,10 @@
79 EPICS_ATOMIC_INLINE size_t epicsAtomicDecrSizeT ( size_t * pTarget )
80 {
81 EpicsAtomicLockKey key;
82+ size_t result;
83+
84 epicsAtomicLock ( & key );
85- const size_t result = --(*pTarget);
86+ result = --(*pTarget);
87 epicsAtomicUnlock ( & key );
88 return result;
89 }
90@@ -85,8 +93,10 @@
91 EPICS_ATOMIC_INLINE int epicsAtomicAddIntT ( int * pTarget, int delta )
92 {
93 EpicsAtomicLockKey key;
94+ int result;
95+
96 epicsAtomicLock ( & key );
97- const int result = *pTarget += delta;
98+ result = *pTarget += delta;
99 epicsAtomicUnlock ( & key );
100 return result;
101 }
102@@ -96,8 +106,10 @@
103 EPICS_ATOMIC_INLINE size_t epicsAtomicAddSizeT ( size_t * pTarget, size_t delta )
104 {
105 EpicsAtomicLockKey key;
106+ size_t result;
107+
108 epicsAtomicLock ( & key );
109- const size_t result = *pTarget += delta;
110+ result = *pTarget += delta;
111 epicsAtomicUnlock ( & key );
112 return result;
113 }
114@@ -107,8 +119,10 @@
115 EPICS_ATOMIC_INLINE size_t epicsAtomicSubSizeT ( size_t * pTarget, size_t delta )
116 {
117 EpicsAtomicLockKey key;
118+ size_t result;
119+
120 epicsAtomicLock ( & key );
121- const size_t result = *pTarget -= delta;
122+ result = *pTarget -= delta;
123 epicsAtomicUnlock ( & key );
124 return result;
125 }
126@@ -176,9 +190,11 @@
127 #ifndef EPICS_ATOMIC_CAS_INTT
128 EPICS_ATOMIC_INLINE int epicsAtomicCmpAndSwapIntT ( int * pTarget, int oldval, int newval )
129 {
130- EpicsAtomicLockKey key;
131+ EpicsAtomicLockKey key;
132+ int cur;
133+
134 epicsAtomicLock ( & key );
135- const int cur = *pTarget;
136+ cur = *pTarget;
137 if ( cur == oldval ) {
138 *pTarget = newval;
139 }
140@@ -191,9 +207,11 @@
141 EPICS_ATOMIC_INLINE size_t epicsAtomicCmpAndSwapSizeT ( size_t * pTarget,
142 size_t oldval, size_t newval )
143 {
144- EpicsAtomicLockKey key;
145+ EpicsAtomicLockKey key;
146+ size_t cur;
147+
148 epicsAtomicLock ( & key );
149- const size_t cur = *pTarget;
150+ cur = *pTarget;
151 if ( cur == oldval ) {
152 *pTarget = newval;
153 }
154@@ -208,8 +226,10 @@
155 EpicsAtomicPtrT oldval, EpicsAtomicPtrT newval )
156 {
157 EpicsAtomicLockKey key;
158+ EpicsAtomicPtrT cur;
159+
160 epicsAtomicLock ( & key );
161- const EpicsAtomicPtrT cur = *pTarget;
162+ cur = *pTarget;
163 if ( cur == oldval ) {
164 *pTarget = newval;
165 }
166
167=== modified file 'src/libCom/osi/epicsSpin.h'
168--- src/libCom/osi/epicsSpin.h 2012-10-29 19:25:23 +0000
169+++ src/libCom/osi/epicsSpin.h 2014-07-25 22:18:29 +0000
170@@ -17,7 +17,8 @@
171
172 typedef struct epicsSpin *epicsSpinId;
173
174-epicsShareFunc epicsSpinId epicsSpinCreate();
175+epicsShareFunc epicsSpinId epicsSpinCreate(void);
176+epicsShareFunc epicsSpinId epicsSpinMustCreate(void);
177 epicsShareFunc void epicsSpinDestroy(epicsSpinId);
178
179 epicsShareFunc void epicsSpinLock(epicsSpinId);
180
181=== modified file 'src/libCom/osi/os/RTEMS/osdSpin.c'
182--- src/libCom/osi/os/RTEMS/osdSpin.c 2013-02-12 18:13:01 +0000
183+++ src/libCom/osi/os/RTEMS/osdSpin.c 2014-07-25 22:18:29 +0000
184@@ -4,6 +4,8 @@
185 * Copyright (c) 2012 ITER Organization.
186 * Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne
187 * National Laboratory.
188+* Copyright (c) 2013 Brookhaven Science Assoc. as Operator of Brookhaven
189+* National Laboratory.
190 * EPICS BASE is distributed subject to a Software License Agreement found
191 * in file LICENSE that is included with this distribution.
192 \*************************************************************************/
193@@ -11,44 +13,88 @@
194 /*
195 * Authors: Ralph Lange <Ralph.Lange@gmx.de>
196 * Andrew Johnson <anj@aps.anl.gov>
197+ * Michael Davidsaver <mdavidsaver@bnl.gov>
198 *
199- * Based on epicsInterrupt.c (RTEMS implementation) by Eric Norum
200+ * Inspired by Linux UP spinlocks implemention
201+ * include/linux/spinlock_api_up.h
202 */
203
204 /*
205 * RTEMS (single CPU): LOCK INTERRUPT
206 *
207 * CAVEAT:
208- * This implementation is for UP architectures only.
209- *
210+ * This implementation is intended for UP architectures only.
211 */
212
213+#define __RTEMS_VIOLATE_KERNEL_VISIBILITY__ 1
214+
215 #include <stdlib.h>
216 #include <rtems.h>
217+#include <cantProceed.h>
218
219 #include "epicsSpin.h"
220
221 typedef struct epicsSpin {
222 rtems_interrupt_level level;
223+ unsigned int locked;
224 } epicsSpin;
225
226-epicsSpinId epicsSpinCreate() {
227+epicsSpinId epicsSpinCreate(void) {
228 return calloc(1, sizeof(epicsSpin));
229 }
230
231+epicsSpinId epicsSpinMustCreate(void)
232+{
233+ epicsSpinId ret = epicsSpinCreate();
234+ if(!ret)
235+ cantProceed("epicsSpinMustCreate fails");
236+ return ret;
237+}
238+
239 void epicsSpinDestroy(epicsSpinId spin) {
240 free(spin);
241 }
242
243 void epicsSpinLock(epicsSpinId spin) {
244- rtems_interrupt_disable(spin->level);
245+ rtems_interrupt_level level;
246+ rtems_interrupt_disable (level);
247+ _Thread_Disable_dispatch();
248+ if(spin->locked) {
249+ rtems_interrupt_enable (level);
250+ _Thread_Enable_dispatch();
251+ if(!rtems_interrupt_is_in_progress()) {
252+ printk("Deadlock in epicsSpinLock(%p). Either recursive lock, missing unlock, or locked by sleeping thread.", spin);
253+ cantProceed(NULL);
254+ } else {
255+ printk("epicsSpinLock(%p) failure in ISR. Either recursive lock, missing unlock, or locked by sleeping thread.\n", spin);
256+ }
257+ return;
258+ }
259+ spin->level = level;
260+ spin->locked = 1;
261 }
262
263 int epicsSpinTryLock(epicsSpinId spin) {
264- epicsSpinLock(spin);
265+ rtems_interrupt_level level;
266+ rtems_interrupt_disable (level);
267+ _Thread_Disable_dispatch();
268+ if(spin->locked) {
269+ rtems_interrupt_enable (level);
270+ _Thread_Enable_dispatch();
271+ return 1;
272+ }
273+ spin->level = level;
274+ spin->locked = 1;
275 return 0;
276 }
277
278 void epicsSpinUnlock(epicsSpinId spin) {
279- rtems_interrupt_enable(spin->level);
280+ rtems_interrupt_level level = spin->level;
281+ if(!spin->locked) {
282+ printk("epicsSpinUnlock(%p) failure. lock not taken\n", spin);
283+ return;
284+ }
285+ spin->level = spin->locked = 0;
286+ rtems_interrupt_enable (level);
287+ _Thread_Enable_dispatch();
288 }
289
290=== modified file 'src/libCom/osi/os/default/osdSpin.c'
291--- src/libCom/osi/os/default/osdSpin.c 2012-11-20 23:27:38 +0000
292+++ src/libCom/osi/os/default/osdSpin.c 2014-07-25 22:18:29 +0000
293@@ -25,7 +25,7 @@
294 epicsMutexId lock;
295 } epicsSpin;
296
297-epicsSpinId epicsSpinCreate() {
298+epicsSpinId epicsSpinCreate(void) {
299 epicsSpin *spin;
300
301 spin = calloc(1, sizeof(*spin));
302@@ -43,6 +43,14 @@
303 return NULL;
304 }
305
306+epicsSpinId epicsSpinMustCreate(void)
307+{
308+ epicsSpinId ret = epicsSpinCreate();
309+ if(!ret)
310+ cantProceed("epicsSpinMustCreate fails");
311+ return ret;
312+}
313+
314 void epicsSpinDestroy(epicsSpinId spin) {
315 epicsMutexDestroy(spin->lock);
316 free(spin);
317
318=== modified file 'src/libCom/osi/os/posix/epicsAtomicOSD.h'
319--- src/libCom/osi/os/posix/epicsAtomicOSD.h 2011-09-02 15:59:03 +0000
320+++ src/libCom/osi/os/posix/epicsAtomicOSD.h 2014-07-25 22:18:29 +0000
321@@ -16,7 +16,7 @@
322 #ifndef epicsAtomicOSD_h
323 #define epicsAtomicOSD_h
324
325-struct EpicsAtomicLockKey {};
326+typedef struct EpicsAtomicLockKey {} EpicsAtomicLockKey;
327
328 #ifdef __cplusplus
329 extern "C" {
330
331=== modified file 'src/libCom/osi/os/posix/osdSpin.c'
332--- src/libCom/osi/os/posix/osdSpin.c 2013-02-18 19:11:36 +0000
333+++ src/libCom/osi/os/posix/osdSpin.c 2014-07-25 22:18:29 +0000
334@@ -18,15 +18,27 @@
335
336 #define epicsExportSharedSymbols
337 #include "errlog.h"
338+#include "cantProceed.h"
339 #include "epicsSpin.h"
340
341+/* POSIX spinlocks may be subject to priority inversion
342+ * and so can't be guaranteed safe in situations where
343+ * threads have different priorities, and thread
344+ * preemption can't be disabled.
345+ */
346+#if defined(DONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING)
347+#if defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1)
348+# define USE_PSPIN
349+#endif
350+#endif
351+
352 #define checkStatus(status,message) \
353 if ((status)) { \
354 errlogPrintf("epicsSpin %s failed: error %s\n", \
355 (message), strerror((status))); \
356 }
357
358-#if defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1)
359+#ifdef USE_PSPIN
360
361 /*
362 * POSIX SPIN LOCKS IMPLEMENTATION
363@@ -36,7 +48,7 @@
364 pthread_spinlock_t lock;
365 } epicsSpin;
366
367-epicsSpinId epicsSpinCreate() {
368+epicsSpinId epicsSpinCreate(void) {
369 epicsSpin *spin;
370 int status;
371
372@@ -70,6 +82,8 @@
373
374 status = pthread_spin_lock(&spin->lock);
375 checkStatus(status, "pthread_spin_lock");
376+ if (status)
377+ cantProceed(NULL);
378 }
379
380 int epicsSpinTryLock(epicsSpinId spin) {
381@@ -79,7 +93,7 @@
382 if (status == EBUSY)
383 return 1;
384 checkStatus(status, "pthread_spin_trylock");
385- return 0;
386+ return status;
387 }
388
389 void epicsSpinUnlock(epicsSpinId spin) {
390@@ -89,7 +103,7 @@
391 checkStatus(status, "pthread_spin_unlock");
392 }
393
394-#else /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */
395+#else /* USE_PSPIN */
396
397 /*
398 * POSIX MUTEX IMPLEMENTATION
399@@ -99,7 +113,7 @@
400 pthread_mutex_t lock;
401 } epicsSpin;
402
403-epicsSpinId epicsSpinCreate() {
404+epicsSpinId epicsSpinCreate(void) {
405 epicsSpin *spin;
406 int status;
407
408@@ -133,6 +147,8 @@
409
410 status = pthread_mutex_lock(&spin->lock);
411 checkStatus(status, "pthread_mutex_lock");
412+ if (status)
413+ cantProceed(NULL);
414 }
415
416 int epicsSpinTryLock(epicsSpinId spin) {
417@@ -142,7 +158,7 @@
418 if (status == EBUSY)
419 return 1;
420 checkStatus(status, "pthread_mutex_trylock");
421- return 0;
422+ return status;
423 }
424
425 void epicsSpinUnlock(epicsSpinId spin) {
426@@ -152,4 +168,13 @@
427 checkStatus(status, "pthread_mutex_unlock");
428 }
429
430-#endif /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */
431+#endif /* USE_PSPIN */
432+
433+
434+epicsSpinId epicsSpinMustCreate(void)
435+{
436+ epicsSpinId ret = epicsSpinCreate();
437+ if(!ret)
438+ cantProceed("epicsSpinMustCreate fails");
439+ return ret;
440+}
441
442=== modified file 'src/libCom/osi/os/solaris/epicsAtomicOSD.h'
443--- src/libCom/osi/os/solaris/epicsAtomicOSD.h 2011-09-07 00:51:04 +0000
444+++ src/libCom/osi/os/solaris/epicsAtomicOSD.h 2014-07-25 22:18:29 +0000
445@@ -159,7 +159,7 @@
446
447 #endif /* ifdef __SunOS_5_10 */
448
449-struct EpicsAtomicLockKey {};
450+typedef struct EpicsAtomicLockKey {} EpicsAtomicLockKey;
451
452 #ifdef __cplusplus
453 extern "C" {
454
455=== modified file 'src/libCom/osi/os/vxWorks/osdSpin.c'
456--- src/libCom/osi/os/vxWorks/osdSpin.c 2012-11-20 21:04:28 +0000
457+++ src/libCom/osi/os/vxWorks/osdSpin.c 2014-07-25 22:18:29 +0000
458@@ -2,18 +2,22 @@
459 * Copyright (c) 2012 Helmholtz-Zentrum Berlin
460 * fuer Materialien und Energie GmbH.
461 * Copyright (c) 2012 ITER Organization.
462+* Copyright (c) 2013 Brookhaven Science Assoc. as Operator of Brookhaven
463+* National Laboratory.
464 * EPICS BASE is distributed subject to a Software License Agreement found
465 * in file LICENSE that is included with this distribution.
466 \*************************************************************************/
467
468 /*
469 * Author: Ralph Lange <Ralph.Lange@gmx.de>
470+ * Michael Davidsaver <mdavidsaver@bnl.gov>
471 *
472- * based on epicsInterrupt.c (vxWorks implementation) by Marty Kraimer
473+ * Inspired by Linux UP spinlocks implemention
474+ * include/linux/spinlock_api_up.h
475 */
476
477 /*
478- * vxWorks (single CPU): LOCK INTERRUPT
479+ * vxWorks (single CPU): LOCK INTERRUPT and DISABLE PREEMPTION
480 *
481 * CAVEAT:
482 * This implementation will not compile on vxWorks SMP architectures.
483@@ -23,30 +27,71 @@
484
485 #include <stdlib.h>
486 #include <intLib.h>
487+#include <taskLib.h>
488
489 #include "epicsSpin.h"
490
491 typedef struct epicsSpin {
492 int key;
493+ unsigned int locked;
494 } epicsSpin;
495
496-epicsSpinId epicsSpinCreate() {
497+epicsSpinId epicsSpinCreate(void) {
498 return calloc(1, sizeof(epicsSpin));
499 }
500
501+epicsSpinId epicsSpinMustCreate(void)
502+{
503+ epicsSpinId ret = epicsSpinCreate();
504+ if(!ret)
505+ cantProceed("epicsSpinMustCreate fails");
506+ return ret;
507+}
508+
509 void epicsSpinDestroy(epicsSpinId spin) {
510 free(spin);
511 }
512
513 void epicsSpinLock(epicsSpinId spin) {
514- spin->key = intLock();
515+ int key = intLock();
516+ if(!intContext())
517+ taskLock();
518+ if(spin->locked) {
519+ intUnlock(key);
520+ if(!intContext()) {
521+ taskUnlock();
522+ cantProceed("Deadlock in epicsSpinLock(). Either recursive lock, missing unlock, or locked by sleeping thread.");
523+ } else {
524+ epicsInterruptContextMessage("epicsSpinLock() failure in ISR. Either recursive lock, missing unlock, or locked by sleeping thread.\n");
525+ }
526+ return;
527+ }
528+ spin->key = key;
529+ spin->locked = 1;
530 }
531
532 int epicsSpinTryLock(epicsSpinId spin) {
533- epicsSpinLock(spin);
534+ int key = intLock();
535+ if(!intContext())
536+ taskLock();
537+ if(spin->locked) {
538+ intUnlock(key);
539+ if(!intContext())
540+ taskUnlock();
541+ return 1;
542+ }
543+ spin->key = key;
544+ spin->locked = 1;
545 return 0;
546 }
547
548 void epicsSpinUnlock(epicsSpinId spin) {
549- intUnlock(spin->key);
550+ int key = spin->key;
551+ if(!spin->locked) {
552+ epicsInterruptContextMessage("epicsSpinUnlock() failure. lock not taken\n");
553+ }
554+ spin->key = spin->locked = 0;
555+ intUnlock(key);
556+ if(!intContext())
557+ taskUnlock();
558 }
559
560=== modified file 'src/libCom/test/epicsRunLibComTests.c'
561--- src/libCom/test/epicsRunLibComTests.c 2013-12-17 18:54:04 +0000
562+++ src/libCom/test/epicsRunLibComTests.c 2014-07-25 22:18:29 +0000
563@@ -18,6 +18,7 @@
564
565 int epicsThreadTest(void);
566 int epicsTimerTest(void);
567+int epicsSpinTest(void);
568 int epicsAlgorithm(void);
569 int epicsEllTest(void);
570 int epicsEnvTest(void);
571@@ -60,6 +61,8 @@
572 */
573 runTest(epicsTimerTest);
574
575+ runTest(epicsSpinTest);
576+
577 runTest(epicsAlgorithm);
578
579 runTest(epicsEllTest);
580
581=== modified file 'src/libCom/test/epicsSpinTest.c'
582--- src/libCom/test/epicsSpinTest.c 2012-11-20 21:55:53 +0000
583+++ src/libCom/test/epicsSpinTest.c 2014-07-25 22:18:29 +0000
584@@ -22,6 +22,7 @@
585
586 #include "epicsTime.h"
587 #include "epicsThread.h"
588+#include "epicsAtomic.h"
589 #include "epicsSpin.h"
590 #include "epicsEvent.h"
591 #include "errlog.h"
592@@ -31,22 +32,26 @@
593 typedef struct info {
594 int threadnum;
595 epicsSpinId spin;
596- int quit;
597+ int *counter;
598+ int rounds;
599+ epicsEventId done;
600 } info;
601
602+#define spinDelay 0.016667
603+
604 void spinThread(void *arg)
605 {
606 info *pinfo = (info *) arg;
607 testDiag("spinThread %d starting", pinfo->threadnum);
608- while (pinfo->quit--) {
609+ epicsThreadSleep(0.1); /* Try to align threads */
610+ while (pinfo->rounds--) {
611+ epicsThreadSleep(spinDelay);
612 epicsSpinLock(pinfo->spin);
613- testPass("spinThread %d epicsSpinLock taken", pinfo->threadnum);
614- epicsThreadSleep(.1);
615+ pinfo->counter[0]++;
616 epicsSpinUnlock(pinfo->spin);
617- epicsThreadSleep(.9);
618 }
619 testDiag("spinThread %d exiting", pinfo->threadnum);
620- return;
621+ epicsEventSignal(pinfo->done);
622 }
623
624 static void lockPair(struct epicsSpin *spin)
625@@ -94,6 +99,8 @@
626
627 /* Initialize spinlock */
628 spin = epicsSpinCreate();
629+ if (!spin)
630+ testAbort("epicsSpinCreate() returned NULL");
631
632 /* test a single lock pair */
633 epicsTimeGetCurrent(&begin);
634@@ -106,78 +113,133 @@
635 delay /= N * 100u; /* convert to delay per lock pair */
636 delay *= 1e6; /* convert to micro seconds */
637 testDiag("lock()*1/unlock()*1 takes %f microseconds", delay);
638+ epicsSpinDestroy(spin);
639 }
640
641+struct verifyTryLock;
642+
643+struct verifyTryLockEnt {
644+ epicsEventId done;
645+ struct verifyTryLock *main;
646+};
647+
648 struct verifyTryLock {
649 epicsSpinId spin;
650- epicsEventId done;
651+ int flag;
652+ struct verifyTryLockEnt *ents;
653 };
654
655 static void verifyTryLockThread(void *pArg)
656 {
657- struct verifyTryLock *pVerify =
658- (struct verifyTryLock *) pArg;
659-
660- testOk1(epicsSpinTryLock(pVerify->spin));
661+ struct verifyTryLockEnt *pVerify =
662+ (struct verifyTryLockEnt *) pArg;
663+
664+ while(epicsAtomicGetIntT(&pVerify->main->flag)==0) {
665+ int ret = epicsSpinTryLock(pVerify->main->spin);
666+ if(ret!=0) {
667+ epicsAtomicCmpAndSwapIntT(&pVerify->main->flag, 0, ret);
668+ break;
669+ } else
670+ epicsSpinUnlock(pVerify->main->spin);
671+ }
672+
673 epicsEventSignal(pVerify->done);
674 }
675
676+/* Start one thread per CPU which will all try lock
677+ * the same spinlock. They break as soon as one
678+ * fails to take the lock.
679+ */
680 static void verifyTryLock()
681 {
682+ int N, i;
683 struct verifyTryLock verify;
684
685- verify.spin = epicsSpinCreate();
686- verify.done = epicsEventMustCreate(epicsEventEmpty);
687-
688- testOk1(epicsSpinTryLock(verify.spin) == 0);
689-
690- epicsThreadCreate("verifyTryLockThread", 40,
691- epicsThreadGetStackSize(epicsThreadStackSmall),
692- verifyTryLockThread, &verify);
693-
694- testOk1(epicsEventWait(verify.done) == epicsEventWaitOK);
695-
696- epicsSpinUnlock(verify.spin);
697+ N = epicsThreadGetCPUs();
698+ if(N==1) {
699+ testSkip(1, "verifyTryLock() only for SMP systems");
700+ return;
701+ }
702+
703+ verify.flag = 0;
704+ verify.spin = epicsSpinMustCreate();
705+
706+ testDiag("Starting %d spinners", N);
707+
708+ verify.ents = calloc(N, sizeof(*verify.ents));
709+ for(i=0; i<N; i++) {
710+ verify.ents[i].main = &verify;
711+ verify.ents[i].done = epicsEventMustCreate(epicsEventEmpty);
712+ epicsThreadMustCreate("verifyTryLockThread", 40,
713+ epicsThreadGetStackSize(epicsThreadStackSmall),
714+ verifyTryLockThread, &verify.ents[i]);
715+ }
716+
717+ testDiag("All started");
718+
719+ for(i=0; i<N; i++) {
720+ epicsEventMustWait(verify.ents[i].done);
721+ epicsEventDestroy(verify.ents[i].done);
722+ }
723+
724+ testDiag("All done");
725+
726+ testOk(verify.flag==1, "epicsTryLock returns %d (expect 1)", verify.flag);
727+
728 epicsSpinDestroy(verify.spin);
729- epicsEventDestroy(verify.done);
730+ free(verify.ents);
731 }
732
733 MAIN(epicsSpinTest)
734 {
735 const int nthreads = 3;
736- const int nrounds = 5;
737+ const int nrounds = 500;
738 unsigned int stackSize;
739 epicsThreadId *id;
740- int i;
741+ int i, counter;
742 char **name;
743- void **arg;
744 info **pinfo;
745 epicsSpinId spin;
746
747- testPlan(3 + nthreads * nrounds);
748+ testPlan(2);
749
750 verifyTryLock();
751
752 spin = epicsSpinCreate();
753+ if (!spin)
754+ testAbort("epicsSpinCreate() returned NULL");
755
756 id = (epicsThreadId *) calloc(nthreads, sizeof(epicsThreadId));
757 name = (char **) calloc(nthreads, sizeof(char *));
758- arg = (void **) calloc(nthreads, sizeof(void *));
759 pinfo = (info **) calloc(nthreads, sizeof(info *));
760 stackSize = epicsThreadGetStackSize(epicsThreadStackSmall);
761+ counter = 0;
762 for (i = 0; i < nthreads; i++) {
763 name[i] = (char *) calloc(10, sizeof(char));
764 sprintf(name[i],"task%d",i);
765 pinfo[i] = (info *) calloc(1, sizeof(info));
766 pinfo[i]->threadnum = i;
767 pinfo[i]->spin = spin;
768- pinfo[i]->quit = nrounds;
769- arg[i] = pinfo[i];
770+ pinfo[i]->counter = &counter;
771+ pinfo[i]->rounds = nrounds;
772+ pinfo[i]->done = epicsEventMustCreate(epicsEventEmpty);
773 id[i] = epicsThreadCreate(name[i], 40, stackSize,
774 spinThread,
775- arg[i]);
776- }
777- epicsThreadSleep(2.0 + nrounds);
778+ pinfo[i]);
779+ }
780+ for (i = 0; i < nthreads; i++) {
781+ epicsEventMustWait(pinfo[i]->done);
782+ epicsEventDestroy(pinfo[i]->done);
783+ free(name[i]);
784+ free(pinfo[i]);
785+ }
786+ testOk(counter == nthreads * nrounds, "Loops run = %d (expecting %d)",
787+ counter, nthreads * nrounds);
788+
789+ free(pinfo);
790+ free(name);
791+ free(id);
792+ epicsSpinDestroy(spin);
793
794 epicsSpinPerformance();
795

Subscribers

People subscribed via source and target branches

to all changes: