diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index 2563546fc..3db8233c0 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -160,12 +160,13 @@ The basic rules which device support must follow are:

The new header file epicsSpin.h adds a portable spin-locks API which is intended for locking very short sections of code (typically one or two lines of C or C++) to provide a critical section that protects against race conditions. -On Posix platforms this uses the pthread_spinlock_t type if it's available but -falls back to a pthread_mutex_t if not; on the UP VxWorks and RTEMS platforms -the implementations lock out the CPU interrupts; the default implementation -(used where no better implementation is available for the platform) uses an -epicsMutex. Spin-locks may not be taken recursively, and the code inside the -critical section should always be short and deterministic.

+On Posix platforms this uses the pthread_spinlock_t type if it's available and +the build is not configured to use Posix thread priorities, but otherwise it +falls back to a pthread_mutex_t. On the UP VxWorks and RTEMS platforms the +implementations lock out CPU interrupts and disable task preemption while a +spin-lock is held. The default implementation (used when no other implementation +is provided) uses an epicsMutex. Spin-locks may not be taken recursively, and +the code inside the critical section should be short and deterministic.

Improvements to aToIPAddr()

diff --git a/src/libCom/osi/Makefile b/src/libCom/osi/Makefile index 94fe92c61..96d4540f2 100644 --- a/src/libCom/osi/Makefile +++ b/src/libCom/osi/Makefile @@ -103,6 +103,7 @@ Com_SRCS += osdStdio.c #POSIX thread priority scheduling flag THREAD_CPPFLAGS_NO += -DDONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING osdThread_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING)) +osdSpin_CPPFLAGS += $(THREAD_CPPFLAGS_$(USE_POSIX_THREAD_PRIORITY_SCHEDULING)) Com_SRCS += osdThread.c Com_SRCS += osdThreadExtra.c diff --git a/src/libCom/osi/epicsAtomicDefault.h b/src/libCom/osi/epicsAtomicDefault.h index ec0975daf..26115ce63 100644 --- a/src/libCom/osi/epicsAtomicDefault.h +++ b/src/libCom/osi/epicsAtomicDefault.h @@ -34,9 +34,11 @@ extern "C" { #ifndef EPICS_ATOMIC_INCR_INTT EPICS_ATOMIC_INLINE int epicsAtomicIncrIntT ( int * pTarget ) { - EpicsAtomicLockKey key; + EpicsAtomicLockKey key; + int result; + epicsAtomicLock ( & key ); - const int result = ++(*pTarget); + result = ++(*pTarget); epicsAtomicUnlock ( & key ); return result; } @@ -46,8 +48,10 @@ EPICS_ATOMIC_INLINE int epicsAtomicIncrIntT ( int * pTarget ) EPICS_ATOMIC_INLINE size_t epicsAtomicIncrSizeT ( size_t * pTarget ) { EpicsAtomicLockKey key; + size_t result; + epicsAtomicLock ( & key ); - const size_t result = ++(*pTarget); + result = ++(*pTarget); epicsAtomicUnlock ( & key ); return result; } @@ -60,8 +64,10 @@ EPICS_ATOMIC_INLINE size_t epicsAtomicIncrSizeT ( size_t * pTarget ) EPICS_ATOMIC_INLINE int epicsAtomicDecrIntT ( int * pTarget ) { EpicsAtomicLockKey key; + int result; + epicsAtomicLock ( & key ); - const int result = --(*pTarget); + result = --(*pTarget); epicsAtomicUnlock ( & key ); return result; } @@ -71,8 +77,10 @@ EPICS_ATOMIC_INLINE int epicsAtomicDecrIntT ( int * pTarget ) EPICS_ATOMIC_INLINE size_t epicsAtomicDecrSizeT ( size_t * pTarget ) { EpicsAtomicLockKey key; + size_t result; + epicsAtomicLock ( & key ); - const size_t result = --(*pTarget); + result = --(*pTarget); epicsAtomicUnlock ( & key ); return result; } @@ -85,8 +93,10 @@ EPICS_ATOMIC_INLINE size_t epicsAtomicDecrSizeT ( size_t * pTarget ) EPICS_ATOMIC_INLINE int epicsAtomicAddIntT ( int * pTarget, int delta ) { EpicsAtomicLockKey key; + int result; + epicsAtomicLock ( & key ); - const int result = *pTarget += delta; + result = *pTarget += delta; epicsAtomicUnlock ( & key ); return result; } @@ -96,8 +106,10 @@ EPICS_ATOMIC_INLINE int epicsAtomicAddIntT ( int * pTarget, int delta ) EPICS_ATOMIC_INLINE size_t epicsAtomicAddSizeT ( size_t * pTarget, size_t delta ) { EpicsAtomicLockKey key; + size_t result; + epicsAtomicLock ( & key ); - const size_t result = *pTarget += delta; + result = *pTarget += delta; epicsAtomicUnlock ( & key ); return result; } @@ -107,8 +119,10 @@ EPICS_ATOMIC_INLINE size_t epicsAtomicAddSizeT ( size_t * pTarget, size_t delta EPICS_ATOMIC_INLINE size_t epicsAtomicSubSizeT ( size_t * pTarget, size_t delta ) { EpicsAtomicLockKey key; + size_t result; + epicsAtomicLock ( & key ); - const size_t result = *pTarget -= delta; + result = *pTarget -= delta; epicsAtomicUnlock ( & key ); return result; } @@ -176,9 +190,11 @@ EPICS_ATOMIC_INLINE EpicsAtomicPtrT #ifndef EPICS_ATOMIC_CAS_INTT EPICS_ATOMIC_INLINE int epicsAtomicCmpAndSwapIntT ( int * pTarget, int oldval, int newval ) { - EpicsAtomicLockKey key; + EpicsAtomicLockKey key; + int cur; + epicsAtomicLock ( & key ); - const int cur = *pTarget; + cur = *pTarget; if ( cur == oldval ) { *pTarget = newval; } @@ -191,9 +207,11 @@ EPICS_ATOMIC_INLINE int epicsAtomicCmpAndSwapIntT ( int * pTarget, int oldval, i EPICS_ATOMIC_INLINE size_t epicsAtomicCmpAndSwapSizeT ( size_t * pTarget, size_t oldval, size_t newval ) { - EpicsAtomicLockKey key; + EpicsAtomicLockKey key; + size_t cur; + epicsAtomicLock ( & key ); - const size_t cur = *pTarget; + cur = *pTarget; if ( cur == oldval ) { *pTarget = newval; } @@ -208,8 +226,10 @@ EPICS_ATOMIC_INLINE EpicsAtomicPtrT epicsAtomicCmpAndSwapPtrT ( EpicsAtomicPtrT oldval, EpicsAtomicPtrT newval ) { EpicsAtomicLockKey key; + EpicsAtomicPtrT cur; + epicsAtomicLock ( & key ); - const EpicsAtomicPtrT cur = *pTarget; + cur = *pTarget; if ( cur == oldval ) { *pTarget = newval; } diff --git a/src/libCom/osi/epicsSpin.h b/src/libCom/osi/epicsSpin.h index 06bc44a09..22ce8ec8f 100644 --- a/src/libCom/osi/epicsSpin.h +++ b/src/libCom/osi/epicsSpin.h @@ -17,7 +17,8 @@ extern "C" { typedef struct epicsSpin *epicsSpinId; -epicsShareFunc epicsSpinId epicsSpinCreate(); +epicsShareFunc epicsSpinId epicsSpinCreate(void); +epicsShareFunc epicsSpinId epicsSpinMustCreate(void); epicsShareFunc void epicsSpinDestroy(epicsSpinId); epicsShareFunc void epicsSpinLock(epicsSpinId); diff --git a/src/libCom/osi/os/RTEMS/osdSpin.c b/src/libCom/osi/os/RTEMS/osdSpin.c index 78600e5f1..7a4546812 100644 --- a/src/libCom/osi/os/RTEMS/osdSpin.c +++ b/src/libCom/osi/os/RTEMS/osdSpin.c @@ -4,51 +4,103 @@ * Copyright (c) 2012 ITER Organization. * Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne * National Laboratory. +* Copyright (c) 2013 Brookhaven Science Assoc. as Operator of Brookhaven +* National Laboratory. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* - * Authors: Ralph Lange - * Andrew Johnson + * Authors: Ralph Lange + * Andrew Johnson + * Michael Davidsaver * - * Based on epicsInterrupt.c (RTEMS implementation) by Eric Norum + * Inspired by Linux UP spinlocks implemention + * include/linux/spinlock_api_up.h */ /* - * RTEMS (single CPU): LOCK INTERRUPT + * RTEMS (single CPU): LOCK INTERRUPT and DISABLE PREEMPTION * * CAVEAT: - * This implementation is for UP architectures only. - * + * This implementation is intended for UP architectures only. */ +#define __RTEMS_VIOLATE_KERNEL_VISIBILITY__ 1 + #include #include +#include "cantProceed.h" #include "epicsSpin.h" typedef struct epicsSpin { rtems_interrupt_level level; + unsigned int locked; } epicsSpin; -epicsSpinId epicsSpinCreate() { +epicsSpinId epicsSpinCreate(void) { return calloc(1, sizeof(epicsSpin)); } +epicsSpinId epicsSpinMustCreate(void) +{ + epicsSpinId ret = epicsSpinCreate(); + if (!ret) + cantProceed("epicsSpinMustCreate: epicsSpinCreate failed."); + return ret; +} + void epicsSpinDestroy(epicsSpinId spin) { free(spin); } void epicsSpinLock(epicsSpinId spin) { - rtems_interrupt_disable(spin->level); + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + _Thread_Disable_dispatch(); + if (spin->locked) { + rtems_interrupt_enable(level); + _Thread_Enable_dispatch(); + if (!rtems_interrupt_is_in_progress()) { + printk("epicsSpinLock(%p): Deadlock.\n", spin); + cantProceed("Recursive lock, missed unlock or block when locked."); + } + else { + printk("epicsSpinLock(%p): Deadlock in ISR.\n" + "Recursive lock, missed unlock or block when locked.\n", + spin); + } + return; + } + spin->level = level; + spin->locked = 1; } int epicsSpinTryLock(epicsSpinId spin) { - epicsSpinLock(spin); + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + _Thread_Disable_dispatch(); + if (spin->locked) { + rtems_interrupt_enable(level); + _Thread_Enable_dispatch(); + return 1; + } + spin->level = level; + spin->locked = 1; return 0; } void epicsSpinUnlock(epicsSpinId spin) { - rtems_interrupt_enable(spin->level); + rtems_interrupt_level level = spin->level; + + if (!spin->locked) { + printk("epicsSpinUnlock(%p): not locked\n", spin); + return; + } + spin->level = spin->locked = 0; + rtems_interrupt_enable (level); + _Thread_Enable_dispatch(); } diff --git a/src/libCom/osi/os/default/osdSpin.c b/src/libCom/osi/os/default/osdSpin.c index 70d9fbace..f4caf4930 100644 --- a/src/libCom/osi/os/default/osdSpin.c +++ b/src/libCom/osi/os/default/osdSpin.c @@ -25,7 +25,7 @@ typedef struct epicsSpin { epicsMutexId lock; } epicsSpin; -epicsSpinId epicsSpinCreate() { +epicsSpinId epicsSpinCreate(void) { epicsSpin *spin; spin = calloc(1, sizeof(*spin)); @@ -43,6 +43,14 @@ fail: return NULL; } +epicsSpinId epicsSpinMustCreate(void) +{ + epicsSpinId ret = epicsSpinCreate(); + if(!ret) + cantProceed("epicsSpinMustCreate fails"); + return ret; +} + void epicsSpinDestroy(epicsSpinId spin) { epicsMutexDestroy(spin->lock); free(spin); diff --git a/src/libCom/osi/os/posix/epicsAtomicOSD.h b/src/libCom/osi/os/posix/epicsAtomicOSD.h index 76c70cd54..ff972016d 100644 --- a/src/libCom/osi/os/posix/epicsAtomicOSD.h +++ b/src/libCom/osi/os/posix/epicsAtomicOSD.h @@ -16,7 +16,7 @@ #ifndef epicsAtomicOSD_h #define epicsAtomicOSD_h -struct EpicsAtomicLockKey {}; +typedef struct EpicsAtomicLockKey {} EpicsAtomicLockKey; #ifdef __cplusplus extern "C" { diff --git a/src/libCom/osi/os/posix/osdSpin.c b/src/libCom/osi/os/posix/osdSpin.c index b8395dbb0..6ebc9f785 100644 --- a/src/libCom/osi/os/posix/osdSpin.c +++ b/src/libCom/osi/os/posix/osdSpin.c @@ -18,15 +18,27 @@ #define epicsExportSharedSymbols #include "errlog.h" +#include "cantProceed.h" #include "epicsSpin.h" +/* POSIX spinlocks may be subject to priority inversion + * and so can't be guaranteed safe in situations where + * threads have different priorities, and thread + * preemption can't be disabled. + */ +#if defined(DONT_USE_POSIX_THREAD_PRIORITY_SCHEDULING) +#if defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) +# define USE_PSPIN +#endif +#endif + #define checkStatus(status,message) \ if ((status)) { \ errlogPrintf("epicsSpin %s failed: error %s\n", \ (message), strerror((status))); \ } -#if defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) +#ifdef USE_PSPIN /* * POSIX SPIN LOCKS IMPLEMENTATION @@ -36,7 +48,7 @@ typedef struct epicsSpin { pthread_spinlock_t lock; } epicsSpin; -epicsSpinId epicsSpinCreate() { +epicsSpinId epicsSpinCreate(void) { epicsSpin *spin; int status; @@ -70,6 +82,8 @@ void epicsSpinLock(epicsSpinId spin) { status = pthread_spin_lock(&spin->lock); checkStatus(status, "pthread_spin_lock"); + if (status) + cantProceed(NULL); } int epicsSpinTryLock(epicsSpinId spin) { @@ -79,7 +93,7 @@ int epicsSpinTryLock(epicsSpinId spin) { if (status == EBUSY) return 1; checkStatus(status, "pthread_spin_trylock"); - return 0; + return status; } void epicsSpinUnlock(epicsSpinId spin) { @@ -89,7 +103,7 @@ void epicsSpinUnlock(epicsSpinId spin) { checkStatus(status, "pthread_spin_unlock"); } -#else /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */ +#else /* USE_PSPIN */ /* * POSIX MUTEX IMPLEMENTATION @@ -99,7 +113,7 @@ typedef struct epicsSpin { pthread_mutex_t lock; } epicsSpin; -epicsSpinId epicsSpinCreate() { +epicsSpinId epicsSpinCreate(void) { epicsSpin *spin; int status; @@ -133,6 +147,8 @@ void epicsSpinLock(epicsSpinId spin) { status = pthread_mutex_lock(&spin->lock); checkStatus(status, "pthread_mutex_lock"); + if (status) + cantProceed(NULL); } int epicsSpinTryLock(epicsSpinId spin) { @@ -142,7 +158,7 @@ int epicsSpinTryLock(epicsSpinId spin) { if (status == EBUSY) return 1; checkStatus(status, "pthread_mutex_trylock"); - return 0; + return status; } void epicsSpinUnlock(epicsSpinId spin) { @@ -152,4 +168,13 @@ void epicsSpinUnlock(epicsSpinId spin) { checkStatus(status, "pthread_mutex_unlock"); } -#endif /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */ +#endif /* USE_PSPIN */ + + +epicsSpinId epicsSpinMustCreate(void) +{ + epicsSpinId ret = epicsSpinCreate(); + if(!ret) + cantProceed("epicsSpinMustCreate fails"); + return ret; +} diff --git a/src/libCom/osi/os/solaris/epicsAtomicOSD.h b/src/libCom/osi/os/solaris/epicsAtomicOSD.h index 5845b6b39..9c7808a49 100644 --- a/src/libCom/osi/os/solaris/epicsAtomicOSD.h +++ b/src/libCom/osi/os/solaris/epicsAtomicOSD.h @@ -159,7 +159,7 @@ EPICS_ATOMIC_INLINE size_t epicsAtomicSubSizeT ( size_t * pTarget, #endif /* ifdef __SunOS_5_10 */ -struct EpicsAtomicLockKey {}; +typedef struct EpicsAtomicLockKey {} EpicsAtomicLockKey; #ifdef __cplusplus extern "C" { diff --git a/src/libCom/osi/os/vxWorks/osdSpin.c b/src/libCom/osi/os/vxWorks/osdSpin.c index 9b545347d..a68cdd374 100644 --- a/src/libCom/osi/os/vxWorks/osdSpin.c +++ b/src/libCom/osi/os/vxWorks/osdSpin.c @@ -2,18 +2,25 @@ * Copyright (c) 2012 Helmholtz-Zentrum Berlin * fuer Materialien und Energie GmbH. * Copyright (c) 2012 ITER Organization. +* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2013 Brookhaven Science Assoc. as Operator of Brookhaven +* National Laboratory. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* - * Author: Ralph Lange + * Authors: Ralph Lange + * Andrew Johnson + * Michael Davidsaver * - * based on epicsInterrupt.c (vxWorks implementation) by Marty Kraimer + * Inspired by Linux UP spinlocks implemention + * include/linux/spinlock_api_up.h */ /* - * vxWorks (single CPU): LOCK INTERRUPT + * vxWorks (single CPU): LOCK INTERRUPT and DISABLE PREEMPTION * * CAVEAT: * This implementation will not compile on vxWorks SMP architectures. @@ -21,32 +28,88 @@ * */ +/* This is needed for vxWorks 6.x to prevent an obnoxious compiler warning */ +#define _VSB_CONFIG_FILE <../lib/h/config/vsbConfig.h> + #include #include +#include +#include +#include "cantProceed.h" #include "epicsSpin.h" typedef struct epicsSpin { int key; + unsigned int locked; } epicsSpin; -epicsSpinId epicsSpinCreate() { +epicsSpinId epicsSpinCreate(void) { return calloc(1, sizeof(epicsSpin)); } +epicsSpinId epicsSpinMustCreate(void) +{ + epicsSpinId ret = epicsSpinCreate(); + if (!ret) + cantProceed("epicsSpinMustCreate: epicsSpinCreate failed."); + return ret; +} + void epicsSpinDestroy(epicsSpinId spin) { free(spin); } void epicsSpinLock(epicsSpinId spin) { - spin->key = intLock(); + int key = intLock(); + + if (!intContext()) + taskLock(); + if (spin->locked) { + intUnlock(key); + if (!intContext()) { + taskUnlock(); + logMsg("epicsSpinLock(%p): Deadlock.\n", + (int) spin, 0, 0, 0, 0, 0); + cantProceed("Recursive lock, missed unlock or block when locked."); + } + else { + logMsg("epicsSpinLock(%p): Deadlock in ISR.\n" + "Recursive lock, missed unlock or block when locked.\n", + (int) spin, 0, 0, 0, 0, 0); + } + return; + } + spin->key = key; + spin->locked = 1; } int epicsSpinTryLock(epicsSpinId spin) { - epicsSpinLock(spin); + int key = intLock(); + + if (!intContext()) + taskLock(); + if (spin->locked) { + intUnlock(key); + if (!intContext()) + taskUnlock(); + return 1; + } + spin->key = key; + spin->locked = 1; return 0; } void epicsSpinUnlock(epicsSpinId spin) { - intUnlock(spin->key); + int key = spin->key; + + if (!spin->locked) { + logMsg("epicsSpinUnlock(%p): not locked\n", + (int) spin, 0, 0, 0, 0, 0); + return; + } + spin->key = spin->locked = 0; + intUnlock(key); + if (!intContext()) + taskUnlock(); } diff --git a/src/libCom/test/epicsRunLibComTests.c b/src/libCom/test/epicsRunLibComTests.c index f59d88b91..a20167e17 100644 --- a/src/libCom/test/epicsRunLibComTests.c +++ b/src/libCom/test/epicsRunLibComTests.c @@ -18,6 +18,7 @@ int epicsThreadTest(void); int epicsTimerTest(void); +int epicsSpinTest(void); int epicsAlgorithm(void); int epicsEllTest(void); int epicsEnvTest(void); @@ -61,6 +62,8 @@ void epicsRunLibComTests(void) */ runTest(epicsTimerTest); + runTest(epicsSpinTest); + runTest(epicsAlgorithm); runTest(epicsEllTest); diff --git a/src/libCom/test/epicsSpinTest.c b/src/libCom/test/epicsSpinTest.c index 707345b89..c45348beb 100644 --- a/src/libCom/test/epicsSpinTest.c +++ b/src/libCom/test/epicsSpinTest.c @@ -22,6 +22,7 @@ #include "epicsTime.h" #include "epicsThread.h" +#include "epicsAtomic.h" #include "epicsSpin.h" #include "epicsEvent.h" #include "errlog.h" @@ -31,22 +32,26 @@ typedef struct info { int threadnum; epicsSpinId spin; - int quit; + int *counter; + int rounds; + epicsEventId done; } info; +#define spinDelay 0.016667 + void spinThread(void *arg) { info *pinfo = (info *) arg; testDiag("spinThread %d starting", pinfo->threadnum); - while (pinfo->quit--) { + epicsThreadSleep(0.1); /* Try to align threads */ + while (pinfo->rounds--) { + epicsThreadSleep(spinDelay); epicsSpinLock(pinfo->spin); - testPass("spinThread %d epicsSpinLock taken", pinfo->threadnum); - epicsThreadSleep(.1); + pinfo->counter[0]++; epicsSpinUnlock(pinfo->spin); - epicsThreadSleep(.9); } testDiag("spinThread %d exiting", pinfo->threadnum); - return; + epicsEventSignal(pinfo->done); } static void lockPair(struct epicsSpin *spin) @@ -94,6 +99,8 @@ void epicsSpinPerformance () /* Initialize spinlock */ spin = epicsSpinCreate(); + if (!spin) + testAbort("epicsSpinCreate() returned NULL"); /* test a single lock pair */ epicsTimeGetCurrent(&begin); @@ -106,78 +113,133 @@ void epicsSpinPerformance () delay /= N * 100u; /* convert to delay per lock pair */ delay *= 1e6; /* convert to micro seconds */ testDiag("lock()*1/unlock()*1 takes %f microseconds", delay); + epicsSpinDestroy(spin); } +struct verifyTryLock; + +struct verifyTryLockEnt { + epicsEventId done; + struct verifyTryLock *main; +}; + struct verifyTryLock { epicsSpinId spin; - epicsEventId done; + int flag; + struct verifyTryLockEnt *ents; }; static void verifyTryLockThread(void *pArg) { - struct verifyTryLock *pVerify = - (struct verifyTryLock *) pArg; + struct verifyTryLockEnt *pVerify = + (struct verifyTryLockEnt *) pArg; + + while(epicsAtomicGetIntT(&pVerify->main->flag)==0) { + int ret = epicsSpinTryLock(pVerify->main->spin); + if(ret!=0) { + epicsAtomicCmpAndSwapIntT(&pVerify->main->flag, 0, ret); + break; + } else + epicsSpinUnlock(pVerify->main->spin); + } - testOk1(epicsSpinTryLock(pVerify->spin)); epicsEventSignal(pVerify->done); } +/* Start one thread per CPU which will all try lock + * the same spinlock. They break as soon as one + * fails to take the lock. + */ static void verifyTryLock() { + int N, i; struct verifyTryLock verify; - verify.spin = epicsSpinCreate(); - verify.done = epicsEventMustCreate(epicsEventEmpty); + N = epicsThreadGetCPUs(); + if(N==1) { + testSkip(1, "verifyTryLock() only for SMP systems"); + return; + } - testOk1(epicsSpinTryLock(verify.spin) == 0); + verify.flag = 0; + verify.spin = epicsSpinMustCreate(); - epicsThreadCreate("verifyTryLockThread", 40, - epicsThreadGetStackSize(epicsThreadStackSmall), - verifyTryLockThread, &verify); + testDiag("Starting %d spinners", N); - testOk1(epicsEventWait(verify.done) == epicsEventWaitOK); + verify.ents = calloc(N, sizeof(*verify.ents)); + for(i=0; ithreadnum = i; pinfo[i]->spin = spin; - pinfo[i]->quit = nrounds; - arg[i] = pinfo[i]; + pinfo[i]->counter = &counter; + pinfo[i]->rounds = nrounds; + pinfo[i]->done = epicsEventMustCreate(epicsEventEmpty); id[i] = epicsThreadCreate(name[i], 40, stackSize, spinThread, - arg[i]); + pinfo[i]); } - epicsThreadSleep(2.0 + nrounds); + for (i = 0; i < nthreads; i++) { + epicsEventMustWait(pinfo[i]->done); + epicsEventDestroy(pinfo[i]->done); + free(name[i]); + free(pinfo[i]); + } + testOk(counter == nthreads * nrounds, "Loops run = %d (expecting %d)", + counter, nthreads * nrounds); + + free(pinfo); + free(name); + free(id); + epicsSpinDestroy(spin); epicsSpinPerformance();