Merged spinlockfix branch.

Some last-minute fixes for VxWorks build warnings.
This commit is contained in:
Andrew Johnson
2014-07-31 12:45:40 -05:00
12 changed files with 311 additions and 75 deletions
+7 -6
View File
@@ -160,12 +160,13 @@ The basic rules which device support must follow are:</p>
<p>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.</p>
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.</p>
<h3>Improvements to aToIPAddr()</h3>
+1
View File
@@ -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
+33 -13
View File
@@ -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;
}
+2 -1
View File
@@ -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);
+62 -10
View File
@@ -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 <Ralph.Lange@gmx.de>
* Andrew Johnson <anj@aps.anl.gov>
* Authors: Ralph Lange <Ralph.Lange@gmx.de>
* Andrew Johnson <anj@aps.anl.gov>
* Michael Davidsaver <mdavidsaver@bnl.gov>
*
* 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 <stdlib.h>
#include <rtems.h>
#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();
}
+9 -1
View File
@@ -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);
+1 -1
View File
@@ -16,7 +16,7 @@
#ifndef epicsAtomicOSD_h
#define epicsAtomicOSD_h
struct EpicsAtomicLockKey {};
typedef struct EpicsAtomicLockKey {} EpicsAtomicLockKey;
#ifdef __cplusplus
extern "C" {
+32 -7
View File
@@ -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;
}
+1 -1
View File
@@ -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" {
+70 -7
View File
@@ -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 <Ralph.Lange@gmx.de>
* Authors: Ralph Lange <Ralph.Lange@gmx.de>
* Andrew Johnson <anj@aps.anl.gov>
* Michael Davidsaver <mdavidsaver@bnl.gov>
*
* 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 <stdlib.h>
#include <intLib.h>
#include <logLib.h>
#include <taskLib.h>
#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();
}
+3
View File
@@ -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);
+90 -28
View File
@@ -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; i<N; i++) {
verify.ents[i].main = &verify;
verify.ents[i].done = epicsEventMustCreate(epicsEventEmpty);
epicsThreadMustCreate("verifyTryLockThread", 40,
epicsThreadGetStackSize(epicsThreadStackSmall),
verifyTryLockThread, &verify.ents[i]);
}
testDiag("All started");
for(i=0; i<N; i++) {
epicsEventMustWait(verify.ents[i].done);
epicsEventDestroy(verify.ents[i].done);
}
testDiag("All done");
testOk(verify.flag==1, "epicsTryLock returns %d (expect 1)", verify.flag);
epicsSpinUnlock(verify.spin);
epicsSpinDestroy(verify.spin);
epicsEventDestroy(verify.done);
free(verify.ents);
}
MAIN(epicsSpinTest)
{
const int nthreads = 3;
const int nrounds = 5;
const int nrounds = 500;
unsigned int stackSize;
epicsThreadId *id;
int i;
int i, counter;
char **name;
void **arg;
info **pinfo;
epicsSpinId spin;
testPlan(3 + nthreads * nrounds);
testPlan(2);
verifyTryLock();
spin = epicsSpinCreate();
if (!spin)
testAbort("epicsSpinCreate() returned NULL");
id = (epicsThreadId *) calloc(nthreads, sizeof(epicsThreadId));
name = (char **) calloc(nthreads, sizeof(char *));
arg = (void **) calloc(nthreads, sizeof(void *));
pinfo = (info **) calloc(nthreads, sizeof(info *));
stackSize = epicsThreadGetStackSize(epicsThreadStackSmall);
counter = 0;
for (i = 0; i < nthreads; i++) {
name[i] = (char *) calloc(10, sizeof(char));
sprintf(name[i],"task%d",i);
pinfo[i] = (info *) calloc(1, sizeof(info));
pinfo[i]->threadnum = 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();