Merge lp:~epics-core/epics-base/spinlockfix into lp:~epics-core/epics-base/3.15
- spinlockfix
- Merge into 3.15
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
mdavidsaver | Needs Resubmitting | ||
Review via email: mp+220845@code.launchpad.net |
Commit message
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 epicsSpinMustCr
Andrew Johnson (anj) wrote : | # |
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
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:/
> 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>
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().
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.
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.
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?
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
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
mdavidsaver (mdavidsaver) wrote : | # |
> > ...I would just pass NULL into the cantProceed() call instead.
>
> Ok.
This is done.
- 12485. By Andrew Johnson
-
Final spinlock tidying-up
* Abort epicsSpinTest() if epicsSpinCreate() returns NULL
* Adjust RELEASE_NOTES that describe the implementations.
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 epicsSpinMustCr
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 - epicsSpinTryLoc
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 - epicsSpinTryLoc
not ok 2 - epicsSpinTryLoc
ok 3 - epicsEventWait(
# 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(
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_
mdavidsaver (mdavidsaver) wrote : | # |
> The main thread called epicsEventWait(
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.
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.
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.
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.
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.
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.
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(
while (pinfo->quit--) {
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
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%
Andrew Johnson (anj) wrote : | # |
Passes on VxWorks, merging.
Preview Diff
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 |
Ping!