From ca800fa57dcfa76d03666571ee6cdaa75ec55e19 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 4 Apr 2018 11:01:45 -0700 Subject: [PATCH 01/25] libCom: add epicsThreadCreateOpt() Variant of epicsThreadCreate() which accepts optional arguments via a structure to allow for compatible addition of new arguments. --- modules/libcom/src/osi/epicsThread.cpp | 11 +++++++ modules/libcom/src/osi/epicsThread.h | 16 +++++++++ modules/libcom/src/osi/os/RTEMS/osdThread.c | 19 ++++++++--- modules/libcom/src/osi/os/WIN32/osdThread.c | 21 +++++++++--- modules/libcom/src/osi/os/posix/osdThread.c | 33 ++++++++++++++----- modules/libcom/src/osi/os/vxWorks/osdThread.c | 24 ++++++++++---- 6 files changed, 100 insertions(+), 24 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 892d73de0..372806edb 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -31,6 +31,17 @@ using namespace std; +epicsThreadId epicsShareAPI epicsThreadCreate ( + const char * name, unsigned int priority, unsigned int stackSize, + EPICSTHREADFUNC funptr,void * parm ) +{ + epicsThreadOpts opts; + opts.priority = priority; + opts.stackSize = stackSize; + + return epicsThreadCreateOpt(name, funptr, parm, &opts); +} + epicsThreadRunable::~epicsThreadRunable () {} void epicsThreadRunable::run () {} void epicsThreadRunable::show ( unsigned int ) const {} diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index 84b2c4788..d0879a8eb 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -65,6 +65,22 @@ epicsShareFunc void epicsThreadRealtimeLock(void); epicsShareFunc void epicsShareAPI epicsThreadExitMain(void); +typedef struct epicsThreadOpts { + /** Thread priority in OSI range (cf. epicsThreadPriority*) */ + unsigned int priority; + /** Thread stack size, as returned by epicsThreadGetStackSize(). + * + * @warning Do not pass enum epicsThreadStackSizeClass directly! + */ + unsigned int stackSize; +} epicsThreadOpts; + +epicsShareFunc void epicsThreadOptsDefaults(epicsThreadOpts *opts); + +epicsShareFunc epicsThreadId epicsThreadCreateOpt ( + const char * name, + EPICSTHREADFUNC funptr, void * parm, + const epicsThreadOpts *opts ); epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate ( const char * name, unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC funptr,void * parm ); diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index 769e95820..7b478df65 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -148,6 +148,13 @@ epicsThreadGetStackSize (epicsThreadStackSizeClass size) return stackSize; } +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 5000}; + +void epicsThreadOptsDefaults(epicsThreadOpts *opts) +{ + *opts = opts_default; +} + /* * Ensure integrity of task variable list */ @@ -263,13 +270,17 @@ void epicsThreadRealtimeLock(void) * Create and start a new thread */ epicsThreadId -epicsThreadCreate (const char *name, - unsigned int priority, unsigned int stackSize, - EPICSTHREADFUNC funptr,void *parm) +epicsThreadCreateOpt ( + const char * name, + EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ) { rtems_id tid; rtems_status_code sc; char c[4]; + unsigned stackSize; + + if(!opts) opts = &opts_default; + stackSize = opts->stackSize; if (!initialized) epicsThreadInit(); if (stackSize < RTEMS_MINIMUM_STACK_SIZE) { @@ -279,7 +290,7 @@ epicsThreadCreate (const char *name, } strncpy (c, name, sizeof c); sc = rtems_task_create (rtems_build_name (c[0], c[1], c[2], c[3]), - epicsThreadGetOssPriorityValue (priority), + epicsThreadGetOssPriorityValue (opts->priority), stackSize, RTEMS_PREEMPT|RTEMS_NO_TIMESLICE|RTEMS_NO_ASR|RTEMS_INTERRUPT_LEVEL(0), RTEMS_FLOATING_POINT|RTEMS_LOCAL, diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index 8cdb4a3f4..f78c7c0f4 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -464,6 +464,13 @@ epicsShareFunc unsigned int epicsShareAPI return stackSizeTable[stackSizeClass]; } +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1)}; + +void epicsThreadOptsDefaults(epicsThreadOpts *opts) +{ + *opts = opts_default; +} + void epicsThreadCleanupWIN32 () { win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal (); @@ -579,8 +586,10 @@ static win32ThreadParam * epicsThreadImplicitCreate ( void ) /* * epicsThreadCreate () */ -epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName, - unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC pFunc,void *pParm) +epicsThreadId epicsThreadCreateOpt ( + const char * pName, + EPICSTHREADFUNC pFunc, void * pParm, + const epicsThreadOpts *opts ) { win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal (); win32ThreadParam * pParmWIN32; @@ -592,18 +601,20 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName, return NULL; } + if(!opts) opts = &opts_default; + pParmWIN32 = epicsThreadParmCreate ( pName ); if ( pParmWIN32 == 0 ) { return ( epicsThreadId ) pParmWIN32; } pParmWIN32->funptr = pFunc; pParmWIN32->parm = pParm; - pParmWIN32->epicsPriority = priority; + pParmWIN32->epicsPriority = opts->priority; { unsigned threadId; pParmWIN32->handle = (HANDLE) _beginthreadex ( - 0, stackSize, epicsWin32ThreadEntry, + 0, opts->stackSize, epicsWin32ThreadEntry, pParmWIN32, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, & threadId ); @@ -615,7 +626,7 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName, pParmWIN32->id = ( DWORD ) threadId ; } - osdPriority = epicsThreadGetOsdPriorityValue (priority); + osdPriority = epicsThreadGetOsdPriorityValue (opts->priority); bstat = SetThreadPriority ( pParmWIN32->handle, osdPriority ); if (!bstat) { CloseHandle ( pParmWIN32->handle ); diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index 755390eed..65e1929ec 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -430,15 +430,22 @@ void epicsThreadRealtimeLock(void) #endif } -epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass) -{ #if defined (OSITHREAD_USE_DEFAULT_STACK) - return 0; +#define STACK_SIZE(f) (0) #elif defined(_POSIX_THREAD_ATTR_STACKSIZE) && _POSIX_THREAD_ATTR_STACKSIZE > 0 #define STACK_SIZE(f) (f * 0x10000 * sizeof(void *)) static const unsigned stackSizeTable[epicsThreadStackBig+1] = { STACK_SIZE(1), STACK_SIZE(2), STACK_SIZE(4) }; +#else +#define STACK_SIZE(f) (0) +#endif /*_POSIX_THREAD_ATTR_STACKSIZE*/ + +epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass) +{ +#if defined (OSITHREAD_USE_DEFAULT_STACK) + return 0; +#elif defined(_POSIX_THREAD_ATTR_STACKSIZE) && _POSIX_THREAD_ATTR_STACKSIZE > 0 if (stackSizeClasspriority,opts->stackSize,funptr,parm); if(pthreadInfo==0) return 0; pthreadInfo->isEpicsThread = 1; setSchedulingPolicy(pthreadInfo,SCHED_FIFO); @@ -518,7 +535,7 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate(const char *name, if(status==EPERM){ /* Try again without SCHED_FIFO*/ free_threadInfo(pthreadInfo); - pthreadInfo = init_threadInfo(name,priority,stackSize,funptr,parm); + pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm); if(pthreadInfo==0) return 0; pthreadInfo->isEpicsThread = 1; status = pthread_create(&pthreadInfo->tid,&pthreadInfo->attr, diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index ce01ea609..6a71301a2 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -133,6 +133,13 @@ unsigned int epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass) return stackSizeTable[stackSizeClass]; } +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 4000*ARCH_STACK_FACTOR}; + +void epicsThreadOptsDefaults(epicsThreadOpts *opts) +{ + *opts = opts_default; +} + struct epicsThreadOSD {}; /* Strictly speaking this should be a WIND_TCB, but we only need it to * be able to create an epicsThreadId that is guaranteed never to be @@ -190,19 +197,22 @@ static void createFunction(EPICSTHREADFUNC func, void *parm) #else #define TASK_FLAGS (VX_FP_TASK) #endif -epicsThreadId epicsThreadCreate(const char *name, - unsigned int priority, unsigned int stackSize, - EPICSTHREADFUNC funptr,void *parm) +epicsThreadId +epicsThreadCreateOpt ( + const char * name, + EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ) { int tid; + if(!opts) opts = &opts_default; + epicsThreadInit(); - if(stackSize<100) { - errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n",name,stackSize); + if(opts->stackSize<100) { + errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n",name,opts->stackSize); return(0); } - tid = taskSpawn((char *)name,getOssPriorityValue(priority), - TASK_FLAGS, stackSize, + tid = taskSpawn((char *)name,getOssPriorityValue(opts->priority), + TASK_FLAGS, opts->stackSize, (FUNCPTR)createFunction,(int)funptr,(int)parm, 0,0,0,0,0,0,0,0); if(tid==ERROR) { From d989c8fade61a2fbff0823276f3a52449022a96c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 4 Apr 2018 11:23:53 -0700 Subject: [PATCH 02/25] libCom: joinable threads (posix only) Add epicsThreadJoin() and epicsThreadOpts::joinable. For compatibility, default threads aren't joinable by default. Currently only POSIX can join. --- modules/libcom/src/osi/epicsThread.cpp | 1 + modules/libcom/src/osi/epicsThread.h | 5 +- modules/libcom/src/osi/os/Linux/osdThread.h | 5 ++ modules/libcom/src/osi/os/RTEMS/osdThread.c | 4 +- modules/libcom/src/osi/os/RTEMS/osdThread.h | 3 + modules/libcom/src/osi/os/WIN32/osdThread.c | 4 +- modules/libcom/src/osi/os/WIN32/osdThread.h | 4 +- modules/libcom/src/osi/os/posix/osdThread.c | 56 ++++++++++++++++--- modules/libcom/src/osi/os/posix/osdThread.h | 5 ++ modules/libcom/src/osi/os/vxWorks/osdThread.c | 4 +- modules/libcom/src/osi/os/vxWorks/osdThread.h | 3 + 11 files changed, 80 insertions(+), 14 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 372806edb..4f983f0e3 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -38,6 +38,7 @@ epicsThreadId epicsShareAPI epicsThreadCreate ( epicsThreadOpts opts; opts.priority = priority; opts.stackSize = stackSize; + opts.joinable = 0; return epicsThreadCreateOpt(name, funptr, parm, &opts); } diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index d0879a8eb..0482bea34 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -73,6 +73,8 @@ typedef struct epicsThreadOpts { * @warning Do not pass enum epicsThreadStackSizeClass directly! */ unsigned int stackSize; + /** Should thread be joinable? (default (0) is not joinable). */ + unsigned int joinable; } epicsThreadOpts; epicsShareFunc void epicsThreadOptsDefaults(epicsThreadOpts *opts); @@ -86,7 +88,8 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate ( EPICSTHREADFUNC funptr,void * parm ); epicsShareFunc epicsThreadId epicsShareAPI epicsThreadMustCreate ( const char * name, unsigned int priority, unsigned int stackSize, - EPICSTHREADFUNC funptr,void * parm ); + EPICSTHREADFUNC funptr,void * parm ); +epicsShareFunc void epicsThreadJoin(epicsThreadId id); epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void); epicsShareFunc void epicsShareAPI epicsThreadResume(epicsThreadId id); epicsShareFunc unsigned int epicsShareAPI epicsThreadGetPriority( diff --git a/modules/libcom/src/osi/os/Linux/osdThread.h b/modules/libcom/src/osi/os/Linux/osdThread.h index 7d2a4868d..40a837e9f 100644 --- a/modules/libcom/src/osi/os/Linux/osdThread.h +++ b/modules/libcom/src/osi/os/Linux/osdThread.h @@ -16,12 +16,16 @@ #include "ellLib.h" #include "epicsEvent.h" +/* This target supports joining threads */ +#define EPICS_THREAD_CAN_JOIN (1) + #ifdef __cplusplus extern "C" { #endif typedef struct epicsThreadOSD { ELLNODE node; + int refcnt; pthread_t tid; pid_t lwpId; pthread_attr_t attr; @@ -35,6 +39,7 @@ typedef struct epicsThreadOSD { int isRealTimeScheduled; int isOnThreadList; unsigned int osiPriority; + int joinable; char name[1]; /* actually larger */ } epicsThreadOSD; diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index 7b478df65..8df2415c0 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -148,7 +148,7 @@ epicsThreadGetStackSize (epicsThreadStackSizeClass size) return stackSize; } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 5000}; +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 5000, 0}; void epicsThreadOptsDefaults(epicsThreadOpts *opts) { @@ -316,6 +316,8 @@ threadMustCreate (const char *name, return tid; } +void epicsThreadJoin(epicsThreadId id) {} + void epicsThreadSuspendSelf (void) { diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.h b/modules/libcom/src/osi/os/RTEMS/osdThread.h index 4451f845a..7307603eb 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.h +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.h @@ -8,5 +8,8 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ +/* This target does not support joining threads */ +#define EPICS_THREAD_CAN_JOIN (0) + int epicsThreadGetOssPriorityValue(unsigned int osiPriority); diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index f78c7c0f4..fb3f39f88 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -464,7 +464,7 @@ epicsShareFunc unsigned int epicsShareAPI return stackSizeTable[stackSizeClass]; } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1)}; +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1), 0}; void epicsThreadOptsDefaults(epicsThreadOpts *opts) { @@ -651,6 +651,8 @@ epicsThreadId epicsThreadCreateOpt ( return ( epicsThreadId ) pParmWIN32; } +void epicsThreadJoin(epicsThreadId id) {} + /* * epicsThreadSuspendSelf () */ diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.h b/modules/libcom/src/osi/os/WIN32/osdThread.h index 136e96bf4..10025d2ad 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.h +++ b/modules/libcom/src/osi/os/WIN32/osdThread.h @@ -11,5 +11,7 @@ #ifndef osdThreadh #define osdThreadh - +/* This target does not support joining threads */ +#define EPICS_THREAD_CAN_JOIN (0) + #endif /* osdThreadh */ diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index 65e1929ec..d34edf694 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -38,6 +38,7 @@ #include "errlog.h" #include "epicsAssert.h" #include "epicsExit.h" +#include "epicsAtomic.h" epicsShareFunc void epicsThreadShowInfo(epicsThreadOSD *pthreadInfo, unsigned int level); epicsShareFunc void osdThreadHooksRun(epicsThreadId id); @@ -167,12 +168,14 @@ static epicsThreadOSD * create_threadInfo(const char *name) return NULL; } strcpy(pthreadInfo->name, name); + epicsAtomicIncrIntT(&pthreadInfo->refcnt); /* initial ref for the thread itself */ return pthreadInfo; } static epicsThreadOSD * init_threadInfo(const char *name, unsigned int priority, unsigned int stackSize, - EPICSTHREADFUNC funptr,void *parm) + EPICSTHREADFUNC funptr,void *parm, + unsigned joinable) { epicsThreadOSD *pthreadInfo; int status; @@ -182,12 +185,15 @@ static epicsThreadOSD * init_threadInfo(const char *name, return NULL; pthreadInfo->createFunc = funptr; pthreadInfo->createArg = parm; + pthreadInfo->joinable = joinable; status = pthread_attr_init(&pthreadInfo->attr); checkStatusOnce(status,"pthread_attr_init"); if(status) return 0; - status = pthread_attr_setdetachstate( - &pthreadInfo->attr, PTHREAD_CREATE_DETACHED); - checkStatusOnce(status,"pthread_attr_setdetachstate"); + if(!joinable){ + status = pthread_attr_setdetachstate( + &pthreadInfo->attr, PTHREAD_CREATE_DETACHED); + checkStatusOnce(status,"pthread_attr_setdetachstate"); + } #if defined (_POSIX_THREAD_ATTR_STACKSIZE) #if ! defined (OSITHREAD_USE_DEFAULT_STACK) status = pthread_attr_setstacksize( &pthreadInfo->attr,(size_t)stackSize); @@ -204,6 +210,8 @@ static void free_threadInfo(epicsThreadOSD *pthreadInfo) { int status; + if(epicsAtomicDecrIntT(&pthreadInfo->refcnt) > 0) return; + status = mutexLock(&listLock); checkStatusQuit(status,"pthread_mutex_lock","free_threadInfo"); if(pthreadInfo->isOnThreadList) ellDelete(&pthreadList,&pthreadInfo->node); @@ -366,7 +374,7 @@ static void once(void) if(errVerbose) fprintf(stderr,"task priorities are not implemented\n"); #endif /* _POSIX_THREAD_PRIORITY_SCHEDULING */ - pthreadInfo = init_threadInfo("_main_",0,epicsThreadGetStackSize(epicsThreadStackSmall),0,0); + pthreadInfo = init_threadInfo("_main_",0,epicsThreadGetStackSize(epicsThreadStackSmall),0,0,0); assert(pthreadInfo!=NULL); status = pthread_setspecific(getpthreadInfo,(void *)pthreadInfo); checkStatusOnceQuit(status,"pthread_setspecific","epicsThreadInit"); @@ -462,7 +470,7 @@ epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize (epicsThreadSt #endif /*_POSIX_THREAD_ATTR_STACKSIZE*/ } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1)}; +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1), 0}; void epicsThreadOptsDefaults(epicsThreadOpts *opts) { @@ -525,7 +533,7 @@ epicsThreadCreateOpt ( assert(pcommonAttr); sigfillset(&blockAllSig); pthread_sigmask(SIG_SETMASK,&blockAllSig,&oldSig); - pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm); + pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm,opts->joinable); if(pthreadInfo==0) return 0; pthreadInfo->isEpicsThread = 1; setSchedulingPolicy(pthreadInfo,SCHED_FIFO); @@ -535,7 +543,7 @@ epicsThreadCreateOpt ( if(status==EPERM){ /* Try again without SCHED_FIFO*/ free_threadInfo(pthreadInfo); - pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm); + pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm,opts->joinable); if(pthreadInfo==0) return 0; pthreadInfo->isEpicsThread = 1; status = pthread_create(&pthreadInfo->tid,&pthreadInfo->attr, @@ -548,6 +556,10 @@ epicsThreadCreateOpt ( } status = pthread_sigmask(SIG_SETMASK,&oldSig,NULL); checkStatusOnce(status,"pthread_sigmask"); + if(pthreadInfo->joinable) { + /* extra ref for epicsThreadJoin() */ + epicsAtomicIncrIntT(&pthreadInfo->refcnt); + } return(pthreadInfo); } @@ -587,7 +599,33 @@ static epicsThreadOSD *createImplicit(void) } return pthreadInfo; } - + +void epicsThreadJoin(epicsThreadId id) +{ + void *ret = NULL; + int status; + + if(!id) { + return; + } else if(!id->joinable) { + /* try to error nicely, however in all likelyhood de-ref of + * 'id' has already caused SIGSEGV as we are racing thread exit, + * which free's 'id'. + */ + cantProceed("%s join not enabled for thread.\n", id->name); + } + + status = pthread_join(id->tid, &ret); + if(status == EDEADLK) { + /* Thread can't join itself (directly or indirectly) + * so we detach instead. + */ + status = pthread_detach(id->tid); + checkStatusOnce(status, "pthread_detach"); + } else checkStatusOnce(status, "pthread_join"); + free_threadInfo(id); +} + epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void) { epicsThreadOSD *pthreadInfo; diff --git a/modules/libcom/src/osi/os/posix/osdThread.h b/modules/libcom/src/osi/os/posix/osdThread.h index 3a80b537c..eee0c825d 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.h +++ b/modules/libcom/src/osi/os/posix/osdThread.h @@ -16,12 +16,16 @@ #include "ellLib.h" #include "epicsEvent.h" +/* This target supports joining threads */ +#define EPICS_THREAD_CAN_JOIN (1) + #ifdef __cplusplus extern "C" { #endif typedef struct epicsThreadOSD { ELLNODE node; + int refcnt; pthread_t tid; pthread_attr_t attr; struct sched_param schedParam; @@ -34,6 +38,7 @@ typedef struct epicsThreadOSD { int isRealTimeScheduled; int isOnThreadList; unsigned int osiPriority; + int joinable; char name[1]; /* actually larger */ } epicsThreadOSD; diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index 6a71301a2..7a0fe7b9f 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -133,7 +133,7 @@ unsigned int epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass) return stackSizeTable[stackSizeClass]; } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 4000*ARCH_STACK_FACTOR}; +static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 4000*ARCH_STACK_FACTOR, 0}; void epicsThreadOptsDefaults(epicsThreadOpts *opts) { @@ -223,6 +223,8 @@ epicsThreadCreateOpt ( return((epicsThreadId)tid); } +void epicsThreadJoin(epicsThreadId id) {} + void epicsThreadSuspendSelf() { STATUS status; diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h index 2ee9f2d46..09704b667 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.h +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h @@ -10,4 +10,7 @@ #ifndef osdThreadh #define osdThreadh +/* This target does not support joining threads */ +#define EPICS_THREAD_CAN_JOIN (0) + #endif /* osdThreadh */ From c9dcab95a6aa7376edde322d3b9473e9a89c2b38 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 4 Apr 2018 11:28:08 -0700 Subject: [PATCH 03/25] class epicsThread is joinable --- modules/libcom/src/osi/epicsThread.cpp | 22 +++++++++++++++++++--- modules/libcom/src/osi/epicsThread.h | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 4f983f0e3..52819c8eb 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -153,6 +153,10 @@ bool epicsThread::exitWait ( const double delay ) throw () if ( this->pThreadDestroyed ) { *this->pThreadDestroyed = true; } + if(!joined) { + epicsThreadJoin(this->id); + joined = true; + } return true; } epicsTime exitWaitBegin = epicsTime::getCurrent (); @@ -166,6 +170,10 @@ bool epicsThread::exitWait ( const double delay ) throw () epicsTime current = epicsTime::getCurrent (); exitWaitElapsed = current - exitWaitBegin; } + if(!joined) { + epicsThreadJoin(this->id); + joined = true; + } } catch ( std :: exception & except ) { errlogPrintf ( @@ -190,10 +198,18 @@ epicsThread::epicsThread ( unsigned stackSize, unsigned priority ) : runable ( runableIn ), id ( 0 ), pThreadDestroyed ( 0 ), begin ( false ), cancel ( false ), terminated ( false ) + , joined(false) { - this->id = epicsThreadCreate ( - pName, priority, stackSize, epicsThreadCallEntryPoint, - static_cast < void * > ( this ) ); + epicsThreadOpts opts; + epicsThreadOptsDefaults(&opts); + opts.stackSize = stackSize; + opts.priority = priority; + opts.joinable = 1; + + this->id = epicsThreadCreateOpt( + pName, epicsThreadCallEntryPoint, + static_cast < void * > ( this ), + &opts); if ( ! this->id ) { throw unableToCreateThread (); } diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index 0482bea34..c43d7fb01 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -197,6 +197,7 @@ private: bool begin; bool cancel; bool terminated; + bool joined; bool beginWait () throw (); epicsThread ( const epicsThread & ); From 460e58e3e5624501a4831eb6f45e9f9d8deb5a78 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 5 Apr 2018 09:04:46 -0700 Subject: [PATCH 04/25] libCom "join" win32 threads Wait for completion --- modules/libcom/src/osi/os/WIN32/osdThread.c | 37 ++++++++++++++++++++- modules/libcom/src/osi/os/WIN32/osdThread.h | 4 +-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index fb3f39f88..eba41645f 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -32,6 +32,7 @@ #include "epicsAssert.h" #include "ellLib.h" #include "epicsExit.h" +#include "epicsAtomic.h" epicsShareFunc void osdThreadHooksRun(epicsThreadId id); @@ -46,6 +47,7 @@ typedef struct win32ThreadGlobal { typedef struct epicsThreadOSD { ELLNODE node; + int refcnt; HANDLE handle; EPICSTHREADFUNC funptr; void * parm; @@ -53,6 +55,7 @@ typedef struct epicsThreadOSD { DWORD id; unsigned epicsPriority; char isSuspended; + char joinable; } win32ThreadParam; typedef struct epicsThreadPrivateOSD { @@ -238,6 +241,8 @@ static void epicsParmCleanupWIN32 ( win32ThreadParam * pParm ) } if ( pParm ) { + if(epicsAtomicDecrIntT(&pParm->refcnt) > 0) return; + /* fprintf ( stderr, "thread %s is exiting\n", pParm->pName ); */ EnterCriticalSection ( & pGbl->mutex ); ellDelete ( & pGbl->threadList, & pParm->node ); @@ -533,6 +538,7 @@ static win32ThreadParam * epicsThreadParmCreate ( const char *pName ) pParmWIN32->pName = (char *) ( pParmWIN32 + 1 ); strcpy ( pParmWIN32->pName, pName ); pParmWIN32->isSuspended = 0; + epicsAtomicIncrIntT(&pParmWIN32->refcnt); } return pParmWIN32; } @@ -648,10 +654,39 @@ epicsThreadId epicsThreadCreateOpt ( return NULL; } + if(opts->joinable) { + pParmWIN32->joinable = 1; + epicsAtomicIncrIntT(&pParmWIN32->refcnt); + } + return ( epicsThreadId ) pParmWIN32; } -void epicsThreadJoin(epicsThreadId id) {} +void epicsThreadJoin(epicsThreadId id) +{ + win32ThreadParam * pParmWIN32 = id; + + if(!id) { + /* no-op */ + } else if(!pParmWIN32->joinable) { + /* try to error nicely, however in all likelyhood de-ref of + * 'pParmWIN32' has already crashed us as we are racing thread exit, + * which free's 'pParmWIN32'. + */ + cantProceed("%s join not enabled for thread.\n", pParmWIN32->pName); + + } else if(epicsThreadGetIdSelf() != id) { + DWORD status = WaitForSingleObject(pParmWIN32->handle, INFINITE); + if(status != WAIT_OBJECT_0) { + /* TODO: signal error? */ + } + + epicsParmCleanupWIN32(pParmWIN32); + } else { + /* join self silently does nothing */ + epicsParmCleanupWIN32(pParmWIN32); + } +} /* * epicsThreadSuspendSelf () diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.h b/modules/libcom/src/osi/os/WIN32/osdThread.h index 10025d2ad..fe60564d1 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.h +++ b/modules/libcom/src/osi/os/WIN32/osdThread.h @@ -11,7 +11,7 @@ #ifndef osdThreadh #define osdThreadh -/* This target does not support joining threads */ -#define EPICS_THREAD_CAN_JOIN (0) +/* This target supports joining threads */ +#define EPICS_THREAD_CAN_JOIN (1) #endif /* osdThreadh */ From 149ab1186ad6e8b8e7a8879bf40d56c905ccce3a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sat, 21 Apr 2018 10:15:35 -0700 Subject: [PATCH 05/25] epicsThread fix join --- modules/libcom/src/osi/epicsThread.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 52819c8eb..7092900b1 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -154,8 +154,11 @@ bool epicsThread::exitWait ( const double delay ) throw () *this->pThreadDestroyed = true; } if(!joined) { + { + epicsGuard < epicsMutex > guard ( this->mutex ); + joined = true; + } epicsThreadJoin(this->id); - joined = true; } return true; } @@ -170,9 +173,11 @@ bool epicsThread::exitWait ( const double delay ) throw () epicsTime current = epicsTime::getCurrent (); exitWaitElapsed = current - exitWaitBegin; } - if(!joined) { - epicsThreadJoin(this->id); + if(this->terminated && !joined) { joined = true; + + epicsGuardRelease < epicsMutex > unguard ( guard ); + epicsThreadJoin(this->id); } } catch ( std :: exception & except ) { From f9092783f89d3626fdbd431457f15a084b2fb884 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sat, 23 Jun 2018 12:36:07 -0700 Subject: [PATCH 06/25] RTEMS thread join --- modules/libcom/RTEMS/rtems_config.c | 1 + modules/libcom/src/osi/os/RTEMS/osdThread.c | 105 +++++++++++++++++--- modules/libcom/src/osi/os/RTEMS/osdThread.h | 4 +- 3 files changed, 96 insertions(+), 14 deletions(-) diff --git a/modules/libcom/RTEMS/rtems_config.c b/modules/libcom/RTEMS/rtems_config.c index 147c08b10..796b1049b 100644 --- a/modules/libcom/RTEMS/rtems_config.c +++ b/modules/libcom/RTEMS/rtems_config.c @@ -27,6 +27,7 @@ #endif #define CONFIGURE_MAXIMUM_TASKS rtems_resource_unlimited(30) +#define CONFIGURE_MAXIMUM_BARRIERS rtems_resource_unlimited(30) #define CONFIGURE_MAXIMUM_SEMAPHORES rtems_resource_unlimited(500) #define CONFIGURE_MAXIMUM_TIMERS rtems_resource_unlimited(20) #define CONFIGURE_MAXIMUM_MESSAGE_QUEUES rtems_resource_unlimited(5) diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index 8df2415c0..a22e2f87f 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -35,6 +35,7 @@ #include "osiUnistd.h" #include "osdInterrupt.h" #include "epicsExit.h" +#include "epicsAtomic.h" epicsShareFunc void osdThreadHooksRun(epicsThreadId id); epicsShareFunc void osdThreadHooksRunMain(epicsThreadId id); @@ -47,6 +48,9 @@ struct taskVar { struct taskVar *back; char *name; rtems_id id; + rtems_id join_barrier; /* only valid if joinable */ + int refcnt; + int joinable; EPICSTHREADFUNC funptr; void *parm; unsigned int threadVariableCapacity; @@ -170,6 +174,22 @@ taskVarUnlock (void) epicsMutexOsdUnlock (taskVarMutex); } +static +void taskUnref(struct taskVar *v) +{ + int ref = epicsAtomicDecrIntT(&v->refcnt); + assert(ref>=0); + if(ref>0) return; + + + if (v->joinable) { + rtems_barrier_delete(v->join_barrier); + } + free (v->threadVariables); + free (v->name); + free (v); +} + /* * EPICS threads destroy themselves by returning from the thread entry function. * This simple wrapper provides the same semantics on RTEMS. @@ -190,9 +210,12 @@ threadWrapper (rtems_task_argument arg) if (v->forw) v->forw->back = v->back; taskVarUnlock (); - free (v->threadVariables); - free (v->name); - free (v); + if(v->joinable) { + rtems_status_code sc = rtems_barrier_wait(v->join_barrier, RTEMS_NO_TIMEOUT); + if(sc!=RTEMS_SUCCESSFUL) + cantProceed("oops %s\n", rtems_status_text(sc)); + } + taskUnref(v); rtems_task_delete (RTEMS_SELF); } @@ -203,21 +226,34 @@ void epicsThreadExitMain (void) { } -static void +static rtems_status_code setThreadInfo(rtems_id tid, const char *name, EPICSTHREADFUNC funptr, - void *parm) + void *parm, int joinable) { struct taskVar *v; uint32_t note; - rtems_status_code sc; + rtems_status_code sc = RTEMS_SUCCESSFUL; v = mallocMustSucceed (sizeof *v, "epicsThreadCreate_vars"); v->name = epicsStrDup(name); v->id = tid; v->funptr = funptr; v->parm = parm; + v->joinable = joinable; + v->refcnt = joinable ? 2 : 1; v->threadVariableCapacity = 0; v->threadVariables = NULL; + if (joinable) { + char c[3]; + strncpy(c, v->name, 3); + sc = rtems_barrier_create(rtems_build_name('~', c[0], c[1], c[2]), + RTEMS_BARRIER_AUTOMATIC_RELEASE | RTEMS_LOCAL, + 2, &v->join_barrier); + if (sc != RTEMS_SUCCESSFUL) { + free(v); + return sc; + } + } note = (uint32_t)v; rtems_task_set_note (tid, RTEMS_NOTEPAD_TASKVAR, note); taskVarLock (); @@ -229,10 +265,14 @@ setThreadInfo(rtems_id tid, const char *name, EPICSTHREADFUNC funptr, taskVarUnlock (); if (funptr) { sc = rtems_task_start (tid, threadWrapper, (rtems_task_argument)v); - if (sc != RTEMS_SUCCESSFUL) - errlogPrintf ("setThreadInfo: Can't start %s: %s\n", - name, rtems_status_text(sc)); } + if (sc != RTEMS_SUCCESSFUL) { + if (joinable) { + rtems_barrier_delete(v->join_barrier); + } + free(v); + } + return sc; } /* @@ -254,7 +294,8 @@ epicsThreadInit (void) if (!onceMutex || !taskVarMutex) cantProceed("epicsThreadInit() can't create global mutexes\n"); rtems_task_ident (RTEMS_SELF, 0, &tid); - setThreadInfo (tid, "_main_", NULL, NULL); + if(setThreadInfo (tid, "_main_", NULL, NULL, 0) != RTEMS_SUCCESSFUL) + cantProceed("epicsThreadInit() unable to setup _main_"); osdThreadHooksRunMain((epicsThreadId)tid); initialized = 1; epicsThreadCreate ("ImsgDaemon", 99, @@ -300,7 +341,13 @@ epicsThreadCreateOpt ( name, rtems_status_text(sc)); return 0; } - setThreadInfo (tid, name, funptr,parm); + sc = setThreadInfo (tid, name, funptr, parm, opts->joinable); + if (sc != RTEMS_SUCCESSFUL) { + errlogPrintf ("epicsThreadCreate create failure during setup for %s: %s\n", + name, rtems_status_text(sc)); + rtems_task_delete(tid); + return 0; + } return (epicsThreadId)tid; } @@ -316,7 +363,41 @@ threadMustCreate (const char *name, return tid; } -void epicsThreadJoin(epicsThreadId id) {} +void epicsThreadJoin(epicsThreadId id) +{ + rtems_id target_tid = (rtems_id)id, self_tid; + struct taskVar *v; + + rtems_task_ident (RTEMS_SELF, 0, &self_tid); + + { + uint32_t note; + rtems_task_get_note (target_tid, RTEMS_NOTEPAD_TASKVAR, ¬e); + v = (void *)note; + } + + if(!v->joinable) { + /* try to error nicely, however in all likelyhood rtems_task_get_note failed, + * or gave us the wrong thread as we are racing thread exit. + */ + cantProceed("%s join not enabled for thread.\n", v->name); + + } else if(target_tid!=self_tid) { + /* wait for target to complete */ + rtems_status_code sc = rtems_barrier_wait(v->join_barrier, RTEMS_NO_TIMEOUT); + if(sc!=RTEMS_SUCCESSFUL) + cantProceed("oopsj %s\n", rtems_status_text(sc)); + + if(sc != RTEMS_SUCCESSFUL) { + errlogPrintf("epicsThreadJoin('%s') -> %s\n", v->name, rtems_status_text(sc)); + } + } + + taskUnref(v); + /* target task may be deleted. + * self task is not deleted, even for self join. + */ +} void epicsThreadSuspendSelf (void) diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.h b/modules/libcom/src/osi/os/RTEMS/osdThread.h index 7307603eb..84d579c0f 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.h +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.h @@ -8,8 +8,8 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ -/* This target does not support joining threads */ -#define EPICS_THREAD_CAN_JOIN (0) +/* This target supports joining threads */ +#define EPICS_THREAD_CAN_JOIN (1) int epicsThreadGetOssPriorityValue(unsigned int osiPriority); From e8db975e7f764c9d6e446de01e307ed27d9313aa Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 17 Jun 2019 14:33:28 -0500 Subject: [PATCH 07/25] Initial implementation for VxWorks 6.9 and later This almost seems too simple... --- modules/libcom/src/osi/os/vxWorks/osdThread.c | 9 ++++++++- modules/libcom/src/osi/os/vxWorks/osdThread.h | 10 ++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index 7a0fe7b9f..8a24add1c 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -223,7 +223,14 @@ epicsThreadCreateOpt ( return((epicsThreadId)tid); } -void epicsThreadJoin(epicsThreadId id) {} +void epicsThreadJoin(epicsThreadId id) { +#if EPICS_THREAD_CAN_JOIN + int tid = (int)id; + + if (tid) + taskWait(tid, WAIT_FOREVER); +#endif +} void epicsThreadSuspendSelf() { diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h index 09704b667..8fa454ce3 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.h +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h @@ -10,7 +10,13 @@ #ifndef osdThreadh #define osdThreadh -/* This target does not support joining threads */ -#define EPICS_THREAD_CAN_JOIN (0) +/* VxWorks 6.9 and later can support joining threads */ + +#if (_WRS_VXWORKS_MAJOR > 6) || \ + (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR >= 9) +# define EPICS_THREAD_CAN_JOIN (1) +#else +# define EPICS_THREAD_CAN_JOIN (0) +#endif #endif /* osdThreadh */ From 5efce9ecc056e5de24c7c8a8cbc2fba7d7f4ef67 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 07:30:48 -0700 Subject: [PATCH 08/25] epicsThreadJoin() -> epicsThreadMustJoin() --- modules/libcom/src/osi/epicsThread.cpp | 4 ++-- modules/libcom/src/osi/epicsThread.h | 2 +- modules/libcom/src/osi/os/RTEMS/osdThread.c | 4 ++-- modules/libcom/src/osi/os/WIN32/osdThread.c | 2 +- modules/libcom/src/osi/os/posix/osdThread.c | 4 ++-- modules/libcom/src/osi/os/vxWorks/osdThread.c | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 7092900b1..9a049133f 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -158,7 +158,7 @@ bool epicsThread::exitWait ( const double delay ) throw () epicsGuard < epicsMutex > guard ( this->mutex ); joined = true; } - epicsThreadJoin(this->id); + epicsThreadMustJoin(this->id); } return true; } @@ -177,7 +177,7 @@ bool epicsThread::exitWait ( const double delay ) throw () joined = true; epicsGuardRelease < epicsMutex > unguard ( guard ); - epicsThreadJoin(this->id); + epicsThreadMustJoin(this->id); } } catch ( std :: exception & except ) { diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index c43d7fb01..61ab323b1 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -89,7 +89,7 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate ( epicsShareFunc epicsThreadId epicsShareAPI epicsThreadMustCreate ( const char * name, unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC funptr,void * parm ); -epicsShareFunc void epicsThreadJoin(epicsThreadId id); +epicsShareFunc void epicsThreadMustJoin(epicsThreadId id); epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void); epicsShareFunc void epicsShareAPI epicsThreadResume(epicsThreadId id); epicsShareFunc unsigned int epicsShareAPI epicsThreadGetPriority( diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index a22e2f87f..cae1b0449 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -363,7 +363,7 @@ threadMustCreate (const char *name, return tid; } -void epicsThreadJoin(epicsThreadId id) +void epicsThreadMustJoin(epicsThreadId id) { rtems_id target_tid = (rtems_id)id, self_tid; struct taskVar *v; @@ -389,7 +389,7 @@ void epicsThreadJoin(epicsThreadId id) cantProceed("oopsj %s\n", rtems_status_text(sc)); if(sc != RTEMS_SUCCESSFUL) { - errlogPrintf("epicsThreadJoin('%s') -> %s\n", v->name, rtems_status_text(sc)); + errlogPrintf("epicsThreadMustJoin('%s') -> %s\n", v->name, rtems_status_text(sc)); } } diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index eba41645f..bc41037cf 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -662,7 +662,7 @@ epicsThreadId epicsThreadCreateOpt ( return ( epicsThreadId ) pParmWIN32; } -void epicsThreadJoin(epicsThreadId id) +void epicsThreadMustJoin(epicsThreadId id) { win32ThreadParam * pParmWIN32 = id; diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index d34edf694..e2f5f556b 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -557,7 +557,7 @@ epicsThreadCreateOpt ( status = pthread_sigmask(SIG_SETMASK,&oldSig,NULL); checkStatusOnce(status,"pthread_sigmask"); if(pthreadInfo->joinable) { - /* extra ref for epicsThreadJoin() */ + /* extra ref for epicsThreadMustJoin() */ epicsAtomicIncrIntT(&pthreadInfo->refcnt); } return(pthreadInfo); @@ -600,7 +600,7 @@ static epicsThreadOSD *createImplicit(void) return pthreadInfo; } -void epicsThreadJoin(epicsThreadId id) +void epicsThreadMustJoin(epicsThreadId id) { void *ret = NULL; int status; diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index 8a24add1c..6847e9769 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -223,7 +223,7 @@ epicsThreadCreateOpt ( return((epicsThreadId)tid); } -void epicsThreadJoin(epicsThreadId id) { +void epicsThreadMustJoin(epicsThreadId id) { #if EPICS_THREAD_CAN_JOIN int tid = (int)id; From 9e5c63fb9158f0262abfc9e6be282dcb542a914c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 07:53:23 -0700 Subject: [PATCH 09/25] epicsThreadMustJoin() clear joinable flag Clear so that repeated calls will error correctly. Only well defined for self join. --- modules/libcom/src/osi/os/RTEMS/osdThread.c | 4 +++- modules/libcom/src/osi/os/WIN32/osdThread.c | 5 ++++- modules/libcom/src/osi/os/posix/osdThread.c | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index cae1b0449..b30cb4156 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -380,7 +380,8 @@ void epicsThreadMustJoin(epicsThreadId id) /* try to error nicely, however in all likelyhood rtems_task_get_note failed, * or gave us the wrong thread as we are racing thread exit. */ - cantProceed("%s join not enabled for thread.\n", v->name); + cantProceed("%s thread not joinable.\n", v->name); + return; } else if(target_tid!=self_tid) { /* wait for target to complete */ @@ -393,6 +394,7 @@ void epicsThreadMustJoin(epicsThreadId id) } } + v->joinable = 0; taskUnref(v); /* target task may be deleted. * self task is not deleted, even for self join. diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index bc41037cf..8c3973703 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -673,7 +673,8 @@ void epicsThreadMustJoin(epicsThreadId id) * 'pParmWIN32' has already crashed us as we are racing thread exit, * which free's 'pParmWIN32'. */ - cantProceed("%s join not enabled for thread.\n", pParmWIN32->pName); + cantProceed("%s thread not joinable.\n", pParmWIN32->pName); + return; } else if(epicsThreadGetIdSelf() != id) { DWORD status = WaitForSingleObject(pParmWIN32->handle, INFINITE); @@ -681,9 +682,11 @@ void epicsThreadMustJoin(epicsThreadId id) /* TODO: signal error? */ } + pParmWIN32->joinable = 0; epicsParmCleanupWIN32(pParmWIN32); } else { /* join self silently does nothing */ + pParmWIN32->joinable = 0; epicsParmCleanupWIN32(pParmWIN32); } } diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index e2f5f556b..ed3785205 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -612,7 +612,8 @@ void epicsThreadMustJoin(epicsThreadId id) * 'id' has already caused SIGSEGV as we are racing thread exit, * which free's 'id'. */ - cantProceed("%s join not enabled for thread.\n", id->name); + cantProceed("%s thread not joinable.\n", id->name); + return; } status = pthread_join(id->tid, &ret); @@ -623,6 +624,7 @@ void epicsThreadMustJoin(epicsThreadId id) status = pthread_detach(id->tid); checkStatusOnce(status, "pthread_detach"); } else checkStatusOnce(status, "pthread_join"); + id->joinable = 0; free_threadInfo(id); } From 86a942872aea66e06cc8aa5478da74f8f5d3fb77 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 08:01:59 -0700 Subject: [PATCH 10/25] epicsThreadTest check explicit join Also remove all the sleeps to improve chances of catching some kind of race. --- modules/libcom/test/epicsThreadTest.cpp | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index eb26cc8bf..c50d8c5bd 100644 --- a/modules/libcom/test/epicsThreadTest.cpp +++ b/modules/libcom/test/epicsThreadTest.cpp @@ -52,7 +52,7 @@ void myThread::run() startEvt.signal(); int *pset = argvalue; privateKey.set(argvalue); - epicsThreadSleep(2.0); + int *pget = privateKey.get(); testOk1(pget == pset); @@ -63,6 +63,7 @@ void myThread::run() typedef struct info { int isOkToBlock; + int didSomething; } info; extern "C" { @@ -71,19 +72,19 @@ static void thread(void *arg) info *pinfo = (info *)arg; epicsThreadSetOkToBlock(pinfo->isOkToBlock); - epicsThreadSleep(1.0); testOk(epicsThreadIsOkToBlock() == pinfo->isOkToBlock, "%s epicsThreadIsOkToBlock() = %d", epicsThreadGetNameSelf(), pinfo->isOkToBlock); - epicsThreadSleep(0.1); + + pinfo->didSomething = 1; } } MAIN(epicsThreadTest) { - testPlan(9); + testPlan(11); unsigned int ncpus = epicsThreadGetCPUs(); testDiag("System has %u CPUs", ncpus); @@ -108,15 +109,23 @@ MAIN(epicsThreadTest) delete myThreads[i]; } - unsigned int stackSize = epicsThreadGetStackSize(epicsThreadStackSmall); + epicsThreadOpts opts; + epicsThreadOptsDefaults(&opts); + opts.priority = 50; + opts.joinable = 1; - info infoA = {0}; - epicsThreadCreate("threadA", 50, stackSize, thread, &infoA); + info infoA = {0, 0}; + epicsThreadId threadA = epicsThreadCreateOpt("threadA", thread, &infoA, &opts); - info infoB = {1}; - epicsThreadCreate("threadB", 50, stackSize, thread, &infoB); + info infoB = {1, 0}; + epicsThreadId threadB = epicsThreadCreateOpt("threadB", thread, &infoB, &opts); - epicsThreadSleep(2.0); + // join B first to better our chance of detecting if it never runs. + epicsThreadMustJoin(threadB); + testOk1(infoB.didSomething); + + epicsThreadMustJoin(threadA); + testOk1(infoA.didSomething); return testDone(); } From 32b3eddb94cf4f47494ff2cc58d6f5e0f3f71d21 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 08:56:32 -0700 Subject: [PATCH 11/25] epicsThreadMustJoin() warn only on double self-join --- modules/libcom/src/osi/os/RTEMS/osdThread.c | 14 ++- modules/libcom/src/osi/os/WIN32/osdThread.c | 15 ++- modules/libcom/src/osi/os/posix/osdThread.c | 15 ++- modules/libcom/test/epicsThreadTest.cpp | 106 ++++++++++++++------ 4 files changed, 108 insertions(+), 42 deletions(-) diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index b30cb4156..b23f66b8f 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -377,10 +377,16 @@ void epicsThreadMustJoin(epicsThreadId id) } if(!v->joinable) { - /* try to error nicely, however in all likelyhood rtems_task_get_note failed, - * or gave us the wrong thread as we are racing thread exit. - */ - cantProceed("%s thread not joinable.\n", v->name); + if(epicsThreadGetIdSelf()==id) { + errlogPrintf("Warning: %s thread self-join of unjoinable\n", v->name); + + } else { + /* try to error nicely, however in all likelyhood de-ref of + * 'id' has already caused SIGSEGV as we are racing thread exit, + * which free's 'id'. + */ + cantProceed("Error: %s thread not joinable.\n", v->name); + } return; } else if(target_tid!=self_tid) { diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index 8c3973703..6d43e769f 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -669,11 +669,16 @@ void epicsThreadMustJoin(epicsThreadId id) if(!id) { /* no-op */ } else if(!pParmWIN32->joinable) { - /* try to error nicely, however in all likelyhood de-ref of - * 'pParmWIN32' has already crashed us as we are racing thread exit, - * which free's 'pParmWIN32'. - */ - cantProceed("%s thread not joinable.\n", pParmWIN32->pName); + if(epicsThreadGetIdSelf()==id) { + fprintf(stderr, "Warning: %s thread self-join of unjoinable\n", pParmWIN32->pName); + + } else { + /* try to error nicely, however in all likelyhood de-ref of + * 'id' has already caused SIGSEGV as we are racing thread exit, + * which free's 'id'. + */ + cantProceed("Error: %s thread not joinable.\n", pParmWIN32->pName); + } return; } else if(epicsThreadGetIdSelf() != id) { diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index ed3785205..5ed2cbc56 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -608,11 +608,16 @@ void epicsThreadMustJoin(epicsThreadId id) if(!id) { return; } else if(!id->joinable) { - /* try to error nicely, however in all likelyhood de-ref of - * 'id' has already caused SIGSEGV as we are racing thread exit, - * which free's 'id'. - */ - cantProceed("%s thread not joinable.\n", id->name); + if(epicsThreadGetIdSelf()==id) { + errlogPrintf("Warning: %s thread self-join of unjoinable\n", id->name); + + } else { + /* try to error nicely, however in all likelyhood de-ref of + * 'id' has already caused SIGSEGV as we are racing thread exit, + * which free's 'id'. + */ + cantProceed("Error: %s thread not joinable.\n", id->name); + } return; } diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index c50d8c5bd..08d7529fa 100644 --- a/modules/libcom/test/epicsThreadTest.cpp +++ b/modules/libcom/test/epicsThreadTest.cpp @@ -18,11 +18,14 @@ #include #include "epicsThread.h" +#include "epicsEvent.h" #include "epicsTime.h" #include "errlog.h" #include "epicsUnitTest.h" #include "testMain.h" +namespace { + static epicsThreadPrivate privateKey; class myThread: public epicsThreadRunable { @@ -60,35 +63,8 @@ void myThread::run() testOk1(thread.getPriority() == epicsThreadGetPriority(self)); } - -typedef struct info { - int isOkToBlock; - int didSomething; -} info; - -extern "C" { -static void thread(void *arg) +void testMyThread() { - info *pinfo = (info *)arg; - - epicsThreadSetOkToBlock(pinfo->isOkToBlock); - - testOk(epicsThreadIsOkToBlock() == pinfo->isOkToBlock, - "%s epicsThreadIsOkToBlock() = %d", - epicsThreadGetNameSelf(), pinfo->isOkToBlock); - - pinfo->didSomething = 1; -} -} - - -MAIN(epicsThreadTest) -{ - testPlan(11); - - unsigned int ncpus = epicsThreadGetCPUs(); - testDiag("System has %u CPUs", ncpus); - testOk1(ncpus > 0); const int ntasks = 3; myThread *myThreads[ntasks]; @@ -108,6 +84,65 @@ MAIN(epicsThreadTest) myThreads[i]->thread.exitWait(); delete myThreads[i]; } +} + +struct selfJoiner { + epicsEvent finished; +}; + +void joiner(void *arg) { + epicsEvent *finished = (epicsEvent*)arg; + + // This is a no-op + epicsThreadMustJoin(epicsThreadGetIdSelf()); + + // This is a no-op as well, except for a warning. + eltc(0); + epicsThreadMustJoin(epicsThreadGetIdSelf()); + eltc(1); + + testPass("Check double self-join"); + finished->signal(); +} + +typedef struct info { + int isOkToBlock; + int didSomething; +} info; + +void testSelfJoin() +{ + epicsEvent finished; + epicsThreadOpts opts; + epicsThreadOptsDefaults(&opts); + opts.priority = 50; + opts.joinable = 1; + + (void)epicsThreadCreateOpt("selfjoin", &joiner, &finished, &opts); + + // as this thread "joins" itself, we can't. + finished.wait(); +} + +} // namespace + +extern "C" { +static void thread(void *arg) +{ + info *pinfo = (info *)arg; + + epicsThreadSetOkToBlock(pinfo->isOkToBlock); + + testOk(epicsThreadIsOkToBlock() == pinfo->isOkToBlock, + "%s epicsThreadIsOkToBlock() = %d", + epicsThreadGetNameSelf(), pinfo->isOkToBlock); + + pinfo->didSomething = 1; +} +} + +static void testOkToBlock() +{ epicsThreadOpts opts; epicsThreadOptsDefaults(&opts); @@ -127,5 +162,20 @@ MAIN(epicsThreadTest) epicsThreadMustJoin(threadA); testOk1(infoA.didSomething); +} + + +MAIN(epicsThreadTest) +{ + testPlan(12); + + unsigned int ncpus = epicsThreadGetCPUs(); + testDiag("System has %u CPUs", ncpus); + testOk1(ncpus > 0); + + testMyThread(); + testSelfJoin(); + testOkToBlock(); + return testDone(); } From afc31f2f064974e97ef61a9dc6cc58692a1b0a5f Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 09:29:04 -0700 Subject: [PATCH 12/25] asCaStop() join worker thread --- modules/database/src/ioc/as/asCa.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/modules/database/src/ioc/as/asCa.c b/modules/database/src/ioc/as/asCa.c index d0180448b..e8c5403d9 100644 --- a/modules/database/src/ioc/as/asCa.c +++ b/modules/database/src/ioc/as/asCa.c @@ -229,20 +229,23 @@ static void asCaTask(void) void asCaStart(void) { + epicsThreadOpts opts; + epicsThreadOptsDefaults(&opts); + opts.stackSize = epicsThreadGetStackSize(epicsThreadStackBig); + opts.priority = epicsThreadPriorityScanLow - 3; + opts.joinable = 1; + if(asCaDebug) printf("asCaStart called\n"); if(firstTime) { - firstTime = FALSE; + firstTime = FALSE; asCaTaskLock=epicsMutexMustCreate(); asCaTaskWait=epicsEventMustCreate(epicsEventEmpty); asCaTaskAddChannels=epicsEventMustCreate(epicsEventEmpty); asCaTaskClearChannels=epicsEventMustCreate(epicsEventEmpty); - threadid = epicsThreadCreate("asCaTask", - (epicsThreadPriorityScanLow - 3), - epicsThreadGetStackSize(epicsThreadStackBig), - (EPICSTHREADFUNC)asCaTask,0); - if(threadid==0) { - errMessage(0,"asCaStart: taskSpawn Failure\n"); - } + threadid = epicsThreadCreateOpt("asCaTask", (EPICSTHREADFUNC)asCaTask, 0, &opts); + if(threadid==0) { + errMessage(0,"asCaStart: taskSpawn Failure\n"); + } } epicsMutexMustLock(asCaTaskLock); epicsEventSignal(asCaTaskAddChannels); @@ -260,6 +263,8 @@ void asCaStop(void) epicsEventMustWait(asCaTaskWait); if(asCaDebug) printf("asCaStop done\n"); epicsMutexUnlock(asCaTaskLock); + epicsThreadMustJoin(threadid); + threadid = 0; } int ascar(int level) { return ascarFP(stdout,level);} From 01fa58f37b4ab74e355fb1bc74b1b1fcaaa96d1a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 09:33:08 -0700 Subject: [PATCH 13/25] dbCa join worker --- modules/database/src/ioc/db/dbCa.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c index 65a8327cf..935c397d4 100644 --- a/modules/database/src/ioc/db/dbCa.c +++ b/modules/database/src/ioc/db/dbCa.c @@ -68,6 +68,7 @@ static volatile enum dbCaCtl_t { ctlInit, ctlRun, ctlPause, ctlExit } dbCaCtl; static epicsEventId startStopEvent; +static epicsThreadId dbCaWorker; struct ca_client_context * dbCaClientContext; @@ -258,10 +259,18 @@ void dbCaShutdown(void) dbCaCtl = ctlExit; epicsEventSignal(workListEvent); epicsEventMustWait(startStopEvent); + if(dbCaWorker) + epicsThreadMustJoin(dbCaWorker); } static void dbCaLinkInitImpl(int isolate) { + epicsThreadOpts opts; + epicsThreadOptsDefaults(&opts); + opts.stackSize = epicsThreadGetStackSize(epicsThreadStackBig); + opts.priority = epicsThreadPriorityMedium; + opts.joinable = 1; + dbServiceIsolate = isolate; dbServiceIOInit(); @@ -274,9 +283,8 @@ static void dbCaLinkInitImpl(int isolate) startStopEvent = epicsEventMustCreate(epicsEventEmpty); dbCaCtl = ctlPause; - epicsThreadCreate("dbCaLink", epicsThreadPriorityMedium, - epicsThreadGetStackSize(epicsThreadStackBig), - dbCaTask, NULL); + dbCaWorker = epicsThreadCreateOpt("dbCaLink", dbCaTask, NULL, &opts); + /* wait for worker to startup and initialize dbCaClientContext */ epicsEventMustWait(startStopEvent); } From 37a76b433a9e7d5a8d26a13fd21ad62f20a0c1c1 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 10:36:52 -0700 Subject: [PATCH 14/25] dbEvent simplify db_close_events() with join --- modules/database/src/ioc/db/dbEvent.c | 71 ++++++++------------------- 1 file changed, 20 insertions(+), 51 deletions(-) diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 9304f99b2..48dc9012b 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -78,7 +78,6 @@ struct event_user { epicsMutexId lock; epicsEventId ppendsem; /* Wait while empty */ epicsEventId pflush_sem; /* wait for flush */ - epicsEventId pexitsem; /* wait for event task to join */ EXTRALABORFUNC *extralabor_sub;/* off load to event task */ void *extralabor_arg;/* parameter to above */ @@ -117,8 +116,6 @@ static char *EVENT_PEND_NAME = "eventTask"; static struct evSubscrip canceledEvent; -static epicsMutexId stopSync; - static unsigned short ringSpace ( const struct event_que *pevq ) { if ( pevq->evque[pevq->putix] == EVENTQEMPTY ) { @@ -260,10 +257,6 @@ dbEventCtx db_init_events (void) { struct event_user * evUser; - if (!stopSync) { - stopSync = epicsMutexMustCreate(); - } - if (!dbevEventUserFreeList) { freeListInitPvt(&dbevEventUserFreeList, sizeof(struct event_user),8); @@ -287,9 +280,6 @@ dbEventCtx db_init_events (void) return NULL; } - /* Flag will be cleared when event task starts */ - evUser->pendexit = TRUE; - evUser->firstque.evUser = evUser; evUser->firstque.writelock = epicsMutexCreate(); if (!evUser->firstque.writelock) @@ -304,9 +294,6 @@ dbEventCtx db_init_events (void) evUser->lock = epicsMutexCreate(); if (!evUser->lock) goto fail; - evUser->pexitsem = epicsEventCreate(epicsEventEmpty); - if (!evUser->pexitsem) - goto fail; evUser->flowCtrlMode = FALSE; evUser->extraLaborBusy = FALSE; @@ -321,8 +308,6 @@ fail: epicsEventDestroy (evUser->ppendsem); if(evUser->pflush_sem) epicsEventDestroy (evUser->pflush_sem); - if(evUser->pexitsem) - epicsEventDestroy (evUser->pexitsem); freeListFree(dbevEventUserFreeList,evUser); return NULL; } @@ -343,7 +328,6 @@ epicsShareFunc void db_cleanup_events(void) dbevFieldLogFreeList = NULL; } - /* intentionally leak stopSync to avoid possible shutdown races */ /* * DB_CLOSE_EVENTS() * @@ -365,30 +349,15 @@ void db_close_events (dbEventCtx ctx) * hazardous to the system's health. */ epicsMutexMustLock ( evUser->lock ); - if(!evUser->pendexit) { /* event task running */ - evUser->pendexit = TRUE; - epicsMutexUnlock ( evUser->lock ); - - /* notify the waiting task */ - epicsEventSignal(evUser->ppendsem); - /* wait for task to exit */ - epicsEventMustWait(evUser->pexitsem); - - epicsMutexMustLock ( evUser->lock ); - } - + evUser->pendexit = TRUE; epicsMutexUnlock ( evUser->lock ); - epicsMutexMustLock (stopSync); + /* notify the waiting task */ + epicsEventSignal(evUser->ppendsem); - epicsEventDestroy(evUser->pexitsem); - epicsEventDestroy(evUser->ppendsem); - epicsEventDestroy(evUser->pflush_sem); - epicsMutexDestroy(evUser->lock); - - epicsMutexUnlock (stopSync); - - freeListFree(dbevEventUserFreeList, evUser); + if(evUser->taskid) + epicsThreadMustJoin(evUser->taskid); + /* evUser has been deleted by the worker */ } /* @@ -1068,17 +1037,14 @@ static void event_task (void *pParm) } } + epicsEventDestroy(evUser->ppendsem); + epicsEventDestroy(evUser->pflush_sem); + epicsMutexDestroy(evUser->lock); + + freeListFree(dbevEventUserFreeList, evUser); + taskwdRemove(epicsThreadGetIdSelf()); - /* use stopSync to ensure pexitsem is not destroy'd - * until epicsEventSignal() has returned. - */ - epicsMutexMustLock (stopSync); - - epicsEventSignal(evUser->pexitsem); - - epicsMutexUnlock(stopSync); - return; } @@ -1090,6 +1056,12 @@ int db_start_events ( void *init_func_arg, unsigned osiPriority ) { struct event_user * const evUser = (struct event_user *) ctx; + epicsThreadOpts opts; + + epicsThreadOptsDefaults(&opts); + opts.stackSize = epicsThreadGetStackSize(epicsThreadStackMedium); + opts.priority = osiPriority; + opts.joinable = 1; epicsMutexMustLock ( evUser->lock ); @@ -1107,15 +1079,12 @@ int db_start_events ( if (!taskname) { taskname = EVENT_PEND_NAME; } - evUser->taskid = epicsThreadCreate ( - taskname, osiPriority, - epicsThreadGetStackSize(epicsThreadStackMedium), - event_task, (void *)evUser); + evUser->taskid = epicsThreadCreateOpt ( + taskname, event_task, (void *)evUser, &opts); if (!evUser->taskid) { epicsMutexUnlock ( evUser->lock ); return DB_EVENT_ERROR; } - evUser->pendexit = FALSE; epicsMutexUnlock ( evUser->lock ); return DB_EVENT_OK; } From 38999a971f78ebacdd2bad69d3886d9ee4c16ec7 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 15:21:56 -0700 Subject: [PATCH 15/25] document epicsThread.h --- modules/libcom/src/osi/epicsThread.h | 128 ++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index 61ab323b1..694ac94c7 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -45,6 +45,7 @@ typedef enum { epicsThreadBooleanStatusFail, epicsThreadBooleanStatusSuccess } epicsThreadBooleanStatus; +/** Lookup target specific default stack size */ epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize( epicsThreadStackSizeClass size); @@ -54,6 +55,20 @@ typedef struct epicsThreadOSD *epicsThreadId; typedef epicsThreadId epicsThreadOnceId; #define EPICS_THREAD_ONCE_INIT 0 +/** Perform one-time initialization. + * + * Run the provided function if it has not run, and is not running. + * + * @post The provided function has been run. + * + * @code + * static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT; + * static void myInitFunc(void *arg) { ... } + * static void some Function(void) { + * epicsThreadOnce(&onceId, &myInitFunc, NULL); + * } + * @endcode + */ epicsShareFunc void epicsShareAPI epicsThreadOnce( epicsThreadOnceId *id, EPICSTHREADFUNC, void *arg); @@ -65,6 +80,7 @@ epicsShareFunc void epicsThreadRealtimeLock(void); epicsShareFunc void epicsShareAPI epicsThreadExitMain(void); +/** For use with epicsThreadCreateOpt() */ typedef struct epicsThreadOpts { /** Thread priority in OSI range (cf. epicsThreadPriority*) */ unsigned int priority; @@ -73,57 +89,123 @@ typedef struct epicsThreadOpts { * @warning Do not pass enum epicsThreadStackSizeClass directly! */ unsigned int stackSize; - /** Should thread be joinable? (default (0) is not joinable). */ + /** Should thread be joinable? (default (0) is not joinable). + * If joinable=1, then epicsThreadMustJoin() must be called for cleanup thread resources. + */ unsigned int joinable; } epicsThreadOpts; +/** Fill in target specific default values. */ epicsShareFunc void epicsThreadOptsDefaults(epicsThreadOpts *opts); +/** @brief Allocate and start a new OS thread. + * @param name A name describing this thread. Appears in various log and error message. + * @param funptr The thread main function. + * @param parm Passed to thread main function. + * @param opts Modifiers for the new thread, or NULL to use target specific defaults. + * @return NULL on error + */ epicsShareFunc epicsThreadId epicsThreadCreateOpt ( const char * name, EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ); +/** Short-hand for epicsThreadCreateOpt() to create an un-joinable thread. */ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate ( const char * name, unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC funptr,void * parm ); +/** Short-hand for epicsThreadCreateOpt() to create an un-joinable thread. + * On error calls cantProceed() + */ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadMustCreate ( const char * name, unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC funptr,void * parm ); +/** Wait for a joinable thread to exit (return from its main function */ epicsShareFunc void epicsThreadMustJoin(epicsThreadId id); +/** Block the current thread until epicsThreadResume(). */ epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void); +/** Resume a thread suspended with epicsThreadSuspendSelf() */ epicsShareFunc void epicsShareAPI epicsThreadResume(epicsThreadId id); +/** Return thread OSI priority */ epicsShareFunc unsigned int epicsShareAPI epicsThreadGetPriority( epicsThreadId id); +/** Return thread OSI priority */ epicsShareFunc unsigned int epicsShareAPI epicsThreadGetPrioritySelf(void); +/** Change OSI priority of target thread. */ epicsShareFunc void epicsShareAPI epicsThreadSetPriority( epicsThreadId id,unsigned int priority); +/** Lookup the next usage OSI priority such that priority > *pPriorityJustBelow + * if this is possible with the current target configuration and privlages. + */ epicsShareFunc epicsThreadBooleanStatus epicsShareAPI epicsThreadHighestPriorityLevelBelow ( unsigned int priority, unsigned *pPriorityJustBelow); +/** Lookup the next usage OSI priority such that priority < *pPriorityJustBelow + * if this is possible with the current target configuration and privlages. + */ epicsShareFunc epicsThreadBooleanStatus epicsShareAPI epicsThreadLowestPriorityLevelAbove ( unsigned int priority, unsigned *pPriorityJustAbove); +/** Test if two thread IDs actually refer to the same OS thread */ epicsShareFunc int epicsShareAPI epicsThreadIsEqual( epicsThreadId id1, epicsThreadId id2); +/** Test if thread has been suspended with epicsThreadSuspendSelf() */ epicsShareFunc int epicsShareAPI epicsThreadIsSuspended(epicsThreadId id); +/** @brief Block the calling thread for at least the specified time. + * @param seconds Time to wait in seconds. Values <=0 blocks for the shortest possible time. + */ epicsShareFunc void epicsShareAPI epicsThreadSleep(double seconds); +/** @brief Query a value approximating the OS timer/scheduler resolution. + * @return A value in seconds >=0 + * + * @warning On targets other than vxWorks and RTEMS, the quantum value often isn't + * meaningful. Use of this function is discouraged in portable code. + */ epicsShareFunc double epicsShareAPI epicsThreadSleepQuantum(void); +/** Find an epicsThreadId associated with the current thread. + * For non-EPICS threads, a new epicsThreadId may be allocated. + */ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadGetIdSelf(void); +/** Attempt to find the first instance of a thread by name. + * @return An epicsThreadId, or NULL if no such thread is currently running. + * Note that a non-NULL ID may still be invalid if this call races + * with thread exit. + * + * @warning Safe use of this function requires external knowledge that this + * thread will not return. + */ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadGetId(const char *name); +/** Return a value approximating the number of threads which this target + * can run in parallel. This value is advisory. + * @return >=1 + */ epicsShareFunc int epicsThreadGetCPUs(void); +/** Return the name of the current thread. + * + * @return Never NULL. Storage lifetime tied to epicsThreadId. + * + * This is either a copy of the string passed to epicsThread*Create*(), + * or an arbitrary unique string for non-EPICS threads. + */ epicsShareFunc const char * epicsShareAPI epicsThreadGetNameSelf(void); -/* For epicsThreadGetName name is guaranteed to be null terminated */ -/* size is size of buffer to hold name (including terminator) */ -/* Failure results in an empty string stored in name */ +/** Copy out the thread name into the provided buffer. + * + * Guaranteed to be null terminated. + * size is number of bytes in buffer to hold name (including terminator). + * Failure results in an empty string stored in name. + */ epicsShareFunc void epicsShareAPI epicsThreadGetName( epicsThreadId id, char *name, size_t size); epicsShareFunc int epicsShareAPI epicsThreadIsOkToBlock(void); epicsShareFunc void epicsShareAPI epicsThreadSetOkToBlock(int isOkToBlock); +/** Print to stdout information about all running EPICS threads. + * @param level 0 prints minimal output. Higher values print more details. + */ epicsShareFunc void epicsShareAPI epicsThreadShowAll(unsigned int level); +/** Print info about a single EPICS thread. */ epicsShareFunc void epicsShareAPI epicsThreadShow( epicsThreadId id,unsigned int level); @@ -134,10 +216,17 @@ epicsShareFunc int epicsThreadHookDelete(EPICS_THREAD_HOOK_ROUTINE hook); epicsShareFunc void epicsThreadHooksShow(void); epicsShareFunc void epicsThreadMap(EPICS_THREAD_HOOK_ROUTINE func); +/** Thread local storage */ typedef struct epicsThreadPrivateOSD * epicsThreadPrivateId; +/** Allocate a new thread local variable. + * This variable will initially hold NULL for each thread. + */ epicsShareFunc epicsThreadPrivateId epicsShareAPI epicsThreadPrivateCreate(void); +/** Free a thread local variable */ epicsShareFunc void epicsShareAPI epicsThreadPrivateDelete(epicsThreadPrivateId id); +/** Update thread local variable */ epicsShareFunc void epicsShareAPI epicsThreadPrivateSet(epicsThreadPrivateId,void *); +/** Fetch the current value of a thread local variable */ epicsShareFunc void * epicsShareAPI epicsThreadPrivateGet(epicsThreadPrivateId); #ifdef __cplusplus @@ -149,33 +238,64 @@ epicsShareFunc void * epicsShareAPI epicsThreadPrivateGet(epicsThreadPrivateId); #include "epicsEvent.h" #include "epicsMutex.h" +//! Interface used with class epicsThread class epicsShareClass epicsThreadRunable { public: virtual ~epicsThreadRunable () = 0; + //! Thread main function. + //! C++ exceptions which propagate from this method will be caught and a warning printed. + //! No other action is taken. virtual void run () = 0; + //! Optional. Called via epicsThread::show() virtual void show ( unsigned int level ) const; }; extern "C" void epicsThreadCallEntryPoint ( void * ); +/** @brief An OS thread + * + * A wrapper around the epicsThread* C API. + * + * @note Threads must be start() ed. + */ class epicsShareClass epicsThread { public: + /** Create a new thread with the provided information. + * + * cf. epicsThreadOpts + * @note Threads must be start() ed. + * @throws epicsThread::unableToCreateThread on error. + */ epicsThread ( epicsThreadRunable &,const char *name, unsigned int stackSize, unsigned int priority=epicsThreadPriorityLow ); ~epicsThread () throw (); + //! Actually start the thread. void start () throw (); + //! Wait for the thread epicsRunnable::run() to return. void exitWait () throw (); + //! Wait for the thread epicsRunnable::run() to return. + //! @param delay Wait up to this many seconds. + //! @returns true if run() returned. false on timeout. bool exitWait ( const double delay ) throw (); + //! @throws A special exitException which will be caught and ignored. + //! @note This exitException doesn't not derive from std::exception static void exit (); + //! cf. epicsThreadResume() void resume () throw (); + //! cf. epicsThreadGetName(); void getName ( char * name, size_t size ) const throw (); + //! cf. epicsThreadGetIdSelf()() epicsThreadId getId () const throw (); + //! cf. epicsThreadGetPriority() unsigned int getPriority () const throw (); + //! cf. epicsThreadSetPriority() void setPriority ( unsigned int ) throw (); bool priorityIsEqual ( const epicsThread & ) const throw (); bool isSuspended () const throw (); + //! @return true if call through this thread's epicsRunnable::run() bool isCurrentThread () const throw (); bool operator == ( const epicsThread & ) const throw (); + //! Say something interesting about this thread to stdout. void show ( unsigned level ) const throw (); /* these operate on the current thread */ From 57d2b143e9ab2a547da14b74fbb4c85ec347da15 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 15:31:35 -0700 Subject: [PATCH 16/25] update release notes --- documentation/RELEASE_NOTES.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index 63f8880a7..fa9727aff 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -26,6 +26,28 @@ release.

--> +

Add epicsThreadCreateOpt() and epicsThreadMustJoin()

+ +

epicsThreadCreateOpt() is an alternative to epicsThreadCreate() which +passes some arguments via a structure (struct epicsThreadOpts). +This epicsThreadOpts* may be NULL to use target specific +defaults. Caller wishing to provide thread options should first call +epicsThreadOptsDefaults() to fill in the defaults.

+ +
+void startitup(void) {
+    epicsThreadOpts opts;
+    epicsThreadOptsDefaults(&opts);
+    opts.priority = epicsThreadPriorityMedium;
+
+    ... = epicsThreadCreateOpt("my thread", &threadMain, NULL, &opts);
+}
+
+ +

If the new epicsThreadOpts::joinable option flag is set (not the default), +then epicsThreadMustJoin() needs to be called to free up thread resources. +This function will block until the thread main function has returned.

+

Launchpad Bugs

The list of tracked bugs fixed in this release can be found on the From 93a96e33c09e6377e112bc3f124d06476f75bde9 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 16:49:41 -0700 Subject: [PATCH 17/25] dbChArrTest use dbUnitTest.h --- modules/database/test/ioc/db/dbChArrTest.cpp | 41 ++++++-------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp index 8a788bed6..8255fdc39 100644 --- a/modules/database/test/ioc/db/dbChArrTest.cpp +++ b/modules/database/test/ioc/db/dbChArrTest.cpp @@ -36,7 +36,7 @@ #include "iocInit.h" #include "iocsh.h" #include "dbChannel.h" -#include "epicsUnitTest.h" +#include "dbUnitTest.h" #include "testMain.h" #include "osiFileName.h" @@ -197,50 +197,33 @@ static void check(short dbr_type) { dbChannelDelete(pch); } -static dbEventCtx evtctx; - -extern "C" { -static void dbChArrTestCleanup(void* junk) -{ - dbFreeBase(pdbbase); - registryFree(); - pdbbase=0; - - db_close_events(evtctx); - - dbmfFreeChunks(); -} -} - MAIN(dbChArrTest) { testPlan(102); /* Prepare the IOC */ + testdbPrepare(); epicsEnvSet("EPICS_CA_SERVER_PORT", server_port); - if (dbReadDatabase(&pdbbase, "dbChArrTest.dbd", - "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR - "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) - testAbort("Database description not loaded"); + testdbReadDatabase("dbChArrTest.dbd", + "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR + "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL); dbChArrTest_registerRecordDeviceDriver(pdbbase); - if (dbReadDatabase(&pdbbase, "dbChArrTest.db", - "." OSI_PATH_LIST_SEPARATOR "..", NULL)) - testAbort("Test database not loaded"); + testdbReadDatabase("dbChArrTest.db", + "." OSI_PATH_LIST_SEPARATOR "..", NULL); - epicsAtExit(&dbChArrTestCleanup,NULL); - - /* Start the IOC */ - - iocInit(); - evtctx = db_init_events(); + testIocInitOk(); check(DBR_LONG); check(DBR_DOUBLE); check(DBR_STRING); + testIocShutdownOk(); + + testdbCleanup(); + return testDone(); } From 14440b2d9d30a25addac00c4e3d227b63def6ecb Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 19:28:03 -0700 Subject: [PATCH 18/25] more RTEMS join --- modules/libcom/src/osi/os/RTEMS/osdThread.c | 7 ++++--- modules/libcom/test/epicsThreadTest.cpp | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index b23f66b8f..fdc31759f 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -366,7 +366,7 @@ threadMustCreate (const char *name, void epicsThreadMustJoin(epicsThreadId id) { rtems_id target_tid = (rtems_id)id, self_tid; - struct taskVar *v; + struct taskVar *v = 0; rtems_task_ident (RTEMS_SELF, 0, &self_tid); @@ -375,10 +375,11 @@ void epicsThreadMustJoin(epicsThreadId id) rtems_task_get_note (target_tid, RTEMS_NOTEPAD_TASKVAR, ¬e); v = (void *)note; } + /* 'v' may be NULL if 'id' represents a non-EPICS thread other than _main_. */ - if(!v->joinable) { + if(!v || !v->joinable) { if(epicsThreadGetIdSelf()==id) { - errlogPrintf("Warning: %s thread self-join of unjoinable\n", v->name); + errlogPrintf("Warning: %s thread self-join of unjoinable\n", v ? v->name : "non-EPICS thread"); } else { /* try to error nicely, however in all likelyhood de-ref of diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index 08d7529fa..edaada914 100644 --- a/modules/libcom/test/epicsThreadTest.cpp +++ b/modules/libcom/test/epicsThreadTest.cpp @@ -172,10 +172,17 @@ MAIN(epicsThreadTest) unsigned int ncpus = epicsThreadGetCPUs(); testDiag("System has %u CPUs", ncpus); testOk1(ncpus > 0); + testDiag("main() thread %p", epicsThreadGetIdSelf()); testMyThread(); testSelfJoin(); testOkToBlock(); + // attempt to self-join from a non-EPICS thread + // to make sure it does nothing as expected + eltc(0); + epicsThreadMustJoin(epicsThreadGetIdSelf()); + eltc(1); + return testDone(); } From ff1462fcc78a326548198afff43d3dff9acebb20 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 28 Jun 2019 12:28:41 -0500 Subject: [PATCH 19/25] Working VxWorks implementation of epicsThreadMustJoin() --- modules/libcom/src/osi/os/vxWorks/osdThread.c | 153 +++++++++++++++--- 1 file changed, 133 insertions(+), 20 deletions(-) diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index 6847e9769..43770eab3 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -34,6 +34,22 @@ #include "vxLib.h" #include "epicsExit.h" +#if EPICS_THREAD_CAN_JOIN + /* The implementation of epicsThreadMustJoin() here uses 2 features + * of VxWorks that were first introduced in VxWorks 6.9: taskWait(), + * and the taskSpareFieldGet/Set routines in taskUtilLib. + */ + #include + + #define JOIN_WARNING_TIMEOUT (60 * sysClkRateGet()) + + static SPARE_NUM joinField; + #define ALLOT_JOIN(tid) taskSpareNumAllot(tid, &joinField) +#else + #define ALLOT_JOIN(tid) +#endif + + epicsShareFunc void osdThreadHooksRun(epicsThreadId id); #if CPU_FAMILY == MC680X0 @@ -109,6 +125,7 @@ static void epicsThreadInit(void) assert(taskIdList); taskIdListSize = ID_LIST_CHUNK; atRebootRegister(); + ALLOT_JOIN(0); done = 1; } lock = 0; @@ -177,19 +194,58 @@ void epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg) } semGive(epicsThreadOnceMutex); } - + +#if EPICS_THREAD_CAN_JOIN + +/* This routine is not static so it appears in the back-trace + * of a thread that is waiting to be joined. + */ +void epicsThreadAwaitingJoin(int tid) +{ + SEM_ID joinSem = (SEM_ID) taskSpareFieldGet(tid, joinField); + STATUS status; + + if (!joinSem || (int) joinSem == ERROR) + return; + + /* Wait for our supervisor */ + status = semTake(joinSem, JOIN_WARNING_TIMEOUT); + if (status && errno == S_objLib_OBJ_TIMEOUT) { + errlogPrintf("Warning: epicsThread '%s' still awaiting join\n", + epicsThreadGetNameSelf()); + status = semTake(joinSem, WAIT_FOREVER); + } + if (status) + perror("epicsThreadAwaitingJoin"); + + semDelete(joinSem); + taskSpareFieldSet(tid, joinField, 0); +} + #define PREPARE_JOIN(tid, joinable) \ + taskSpareFieldSet(tid, joinField, \ + joinable ? (int) semBCreate(SEM_Q_FIFO, SEM_EMPTY) : 0) + #define AWAIT_JOIN(tid) epicsThreadAwaitingJoin(tid) +#else + #define PREPARE_JOIN(tid, joinable) + #define AWAIT_JOIN(tid) +#endif + static void createFunction(EPICSTHREADFUNC func, void *parm) { int tid = taskIdSelf(); taskVarAdd(tid,(int *)(char *)&papTSD); - /*Make sure that papTSD is still 0 after that call to taskVarAdd*/ - papTSD = 0; + papTSD = NULL; /* Initialize for this thread */ + osdThreadHooksRun((epicsThreadId)tid); + (*func)(parm); + epicsExitCallAtThreadExits (); free(papTSD); taskVarDelete(tid,(int *)(char *)&papTSD); + + AWAIT_JOIN(tid); } #ifdef ALTIVEC @@ -197,38 +253,95 @@ static void createFunction(EPICSTHREADFUNC func, void *parm) #else #define TASK_FLAGS (VX_FP_TASK) #endif -epicsThreadId -epicsThreadCreateOpt ( - const char * name, +epicsThreadId epicsThreadCreateOpt(const char * name, EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ) { int tid; - if(!opts) opts = &opts_default; + if (!opts) + opts = &opts_default; epicsThreadInit(); - if(opts->stackSize<100) { - errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n",name,opts->stackSize); - return(0); + if (opts->stackSize < 100) { + errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n", + name, opts->stackSize); + return 0; } - tid = taskSpawn((char *)name,getOssPriorityValue(opts->priority), + + tid = taskCreate((char *)name,getOssPriorityValue(opts->priority), TASK_FLAGS, opts->stackSize, - (FUNCPTR)createFunction,(int)funptr,(int)parm, + (FUNCPTR)createFunction, (int)funptr, (int)parm, 0,0,0,0,0,0,0,0); - if(tid==ERROR) { + if (tid == ERROR) { errlogPrintf("epicsThreadCreate %s failure %s\n", - name,strerror(errno)); - return(0); + name, strerror(errno)); + return 0; } - return((epicsThreadId)tid); + + PREPARE_JOIN(tid, opts->joinable); + taskActivate(tid); + + return (epicsThreadId)tid; } -void epicsThreadMustJoin(epicsThreadId id) { +void epicsThreadMustJoin(epicsThreadId id) +{ + const char *fn = "epicsThreadMustJoin"; #if EPICS_THREAD_CAN_JOIN - int tid = (int)id; + int tid = (int) id; + SEM_ID joinSem; + STATUS status; - if (tid) - taskWait(tid, WAIT_FOREVER); + if (!tid) + return; + + joinSem = (SEM_ID) taskSpareFieldGet(tid, joinField); + if ((int) joinSem == ERROR) { + errlogPrintf("%s: Thread '%s' no longer exists.\n", + fn, taskName(tid)); + return; + } + + if (tid == taskIdSelf()) { + if (joinSem) { + semDelete(joinSem); + taskSpareFieldSet(tid, joinField, 0); + } + else { + errlogPrintf("%s: Self-join of unjoinable thread '%s'\n", + fn, taskName(tid)); + } + return; + } + + if (!joinSem) { + cantProceed("%s: Thread '%s' is not joinable.\n", + fn, taskName(tid)); + return; + } + + semGive(joinSem); /* Rendezvous with thread */ + + status = taskWait(tid, JOIN_WARNING_TIMEOUT); + if (status && errno == S_objLib_OBJ_TIMEOUT) { + errlogPrintf("Warning: %s still waiting for thread '%s'\n", + fn, taskName(tid)); + status = taskWait(tid, WAIT_FOREVER); + } + if (status) { + if (errno == S_taskLib_ILLEGAL_OPERATION) { + errlogPrintf("%s: This shouldn't happen!\n", fn); + } + else if (errno == S_objLib_OBJ_ID_ERROR) { + errlogPrintf("%s: %x is not a known thread\n", fn, tid); + } + else { + perror(fn); + } + cantProceed(fn); + } +#else + cantProceed("%s called when EPICS_THREAD_CAN_JOIN is 0\n", fn); #endif } From 8a072d3c043c2ba51214a7e56d550ff4c9dd95e4 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 28 Jun 2019 12:40:07 -0500 Subject: [PATCH 20/25] More basic tests of epicsThreadMustJoin() --- modules/libcom/test/epicsThreadTest.cpp | 67 ++++++++++++++++++------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index edaada914..6785f32ba 100644 --- a/modules/libcom/test/epicsThreadTest.cpp +++ b/modules/libcom/test/epicsThreadTest.cpp @@ -86,46 +86,75 @@ void testMyThread() } } -struct selfJoiner { - epicsEvent finished; +struct joinStuff { + epicsThreadOpts *opts; + epicsEvent *trigger; + epicsEvent *finished; }; -void joiner(void *arg) { - epicsEvent *finished = (epicsEvent*)arg; +void donothing(void *arg) +{} + +void dowait(void *arg) +{ + epicsEvent *trigger = (epicsEvent *) arg; + trigger->wait(); + epicsThreadSleep(0.1); +} + +void joinTests(void *arg) +{ + struct joinStuff *stuff = (struct joinStuff *) arg; + + // Task finishes before parent joins + epicsThreadId tid = epicsThreadCreateOpt("nothing", + &donothing, 0, stuff->opts); + epicsThreadSleep(0.1); + epicsThreadMustJoin(tid); + + // Parent joins before task finishes + tid = epicsThreadCreateOpt("await", + &dowait, stuff->trigger, stuff->opts); + stuff->trigger->signal(); + epicsThreadMustJoin(tid); // This is a no-op - epicsThreadMustJoin(epicsThreadGetIdSelf()); + epicsThreadId self = epicsThreadGetIdSelf(); + epicsThreadMustJoin(self); // This is a no-op as well, except for a warning. eltc(0); - epicsThreadMustJoin(epicsThreadGetIdSelf()); + epicsThreadMustJoin(self); eltc(1); - testPass("Check double self-join"); - finished->signal(); + stuff->finished->signal(); } -typedef struct info { - int isOkToBlock; - int didSomething; -} info; - -void testSelfJoin() +void testJoining() { - epicsEvent finished; epicsThreadOpts opts; epicsThreadOptsDefaults(&opts); opts.priority = 50; opts.joinable = 1; - (void)epicsThreadCreateOpt("selfjoin", &joiner, &finished, &opts); + epicsEvent finished, trigger; - // as this thread "joins" itself, we can't. - finished.wait(); + struct joinStuff stuff = { + &opts, &trigger, &finished + }; + epicsThreadCreateOpt("parent", &joinTests, &stuff, &opts); + + // as selfjoin joins itself, we can't. + testOk(finished.wait(10.0), "Join tests completed"); } } // namespace +typedef struct info { + int isOkToBlock; + int didSomething; +} info; + extern "C" { static void thread(void *arg) { @@ -174,8 +203,8 @@ MAIN(epicsThreadTest) testOk1(ncpus > 0); testDiag("main() thread %p", epicsThreadGetIdSelf()); + testJoining(); // Do this first, ~epicsThread() uses it... testMyThread(); - testSelfJoin(); testOkToBlock(); // attempt to self-join from a non-EPICS thread From 4b77d5e1c98f29a70129cf72e02145f68cf220d6 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 28 Jun 2019 13:24:29 -0500 Subject: [PATCH 21/25] Release Note updates VxWorks minimum version, and more about joinable threads. --- documentation/RELEASE_NOTES.html | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index fa9727aff..b3e266031 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -26,6 +26,13 @@ release.

--> +

VxWorks Minimum Version Requirements

+ +

The implementation of the epicsThreadMustJoin() functionality +described below requires facilities that were added to the OS in VxWorks 6.9, so +that is now the oldest version which this release of EPICS can be built +against.

+

Add epicsThreadCreateOpt() and epicsThreadMustJoin()

epicsThreadCreateOpt() is an alternative to epicsThreadCreate() which @@ -45,8 +52,16 @@ void startitup(void) {

If the new epicsThreadOpts::joinable option flag is set (not the default), -then epicsThreadMustJoin() needs to be called to free up thread resources. -This function will block until the thread main function has returned.

+then epicsThreadMustJoin() must be called with that +thread's epicsThreadId when/after the thread exits, to free up thread resources. +This function will block until the thread's main function has returned, after +which the epicsThreadId will no longer be valid.

+ +

A thread which was created with the joinable flag set may itself call +epicsThreadMustJoin() passing its own epicsThreadId. This marks the +thread as no longer being joinable, so it will then free the thread resources +itself when its main function returns. The epicsThreadId for a thread that is +not joinable gets invalidated as soon as its main function returns.

Launchpad Bugs

From fbf62189cbabdc96b9e431ed60b0557e0c6079ea Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 2 Jul 2019 17:27:27 -0500 Subject: [PATCH 22/25] Replace epicsThreadOptsDefaults() with EPICS_THREAD_OPTS_INIT The epicsThreadCreate() routines now interpose calls to epicsThreadGetStackSize() if an enum value is passed. --- modules/database/src/ioc/as/asCa.c | 4 +- modules/database/src/ioc/db/dbCa.c | 4 +- modules/database/src/ioc/db/dbEvent.c | 3 +- modules/libcom/src/osi/epicsThread.cpp | 9 ++- modules/libcom/src/osi/epicsThread.h | 15 +++-- modules/libcom/src/osi/os/RTEMS/osdThread.c | 20 +++--- modules/libcom/src/osi/os/WIN32/osdThread.c | 18 ++--- modules/libcom/src/osi/os/posix/osdThread.c | 66 +++++++++++-------- modules/libcom/src/osi/os/vxWorks/osdThread.c | 26 ++++---- modules/libcom/test/epicsThreadTest.cpp | 16 ++--- 10 files changed, 95 insertions(+), 86 deletions(-) diff --git a/modules/database/src/ioc/as/asCa.c b/modules/database/src/ioc/as/asCa.c index e8c5403d9..21bb47f5f 100644 --- a/modules/database/src/ioc/as/asCa.c +++ b/modules/database/src/ioc/as/asCa.c @@ -229,8 +229,8 @@ static void asCaTask(void) void asCaStart(void) { - epicsThreadOpts opts; - epicsThreadOptsDefaults(&opts); + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + opts.stackSize = epicsThreadGetStackSize(epicsThreadStackBig); opts.priority = epicsThreadPriorityScanLow - 3; opts.joinable = 1; diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c index 935c397d4..ea0fad0d3 100644 --- a/modules/database/src/ioc/db/dbCa.c +++ b/modules/database/src/ioc/db/dbCa.c @@ -265,8 +265,8 @@ void dbCaShutdown(void) static void dbCaLinkInitImpl(int isolate) { - epicsThreadOpts opts; - epicsThreadOptsDefaults(&opts); + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + opts.stackSize = epicsThreadGetStackSize(epicsThreadStackBig); opts.priority = epicsThreadPriorityMedium; opts.joinable = 1; diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 48dc9012b..febe62634 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -1056,9 +1056,8 @@ int db_start_events ( void *init_func_arg, unsigned osiPriority ) { struct event_user * const evUser = (struct event_user *) ctx; - epicsThreadOpts opts; + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; - epicsThreadOptsDefaults(&opts); opts.stackSize = epicsThreadGetStackSize(epicsThreadStackMedium); opts.priority = osiPriority; opts.joinable = 1; diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 9a049133f..92f833847 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -35,7 +35,7 @@ epicsThreadId epicsShareAPI epicsThreadCreate ( const char * name, unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC funptr,void * parm ) { - epicsThreadOpts opts; + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; opts.priority = priority; opts.stackSize = stackSize; opts.joinable = 0; @@ -202,11 +202,10 @@ epicsThread::epicsThread ( epicsThreadRunable & runableIn, const char * pName, unsigned stackSize, unsigned priority ) : runable ( runableIn ), id ( 0 ), pThreadDestroyed ( 0 ), - begin ( false ), cancel ( false ), terminated ( false ) - , joined(false) + begin ( false ), cancel ( false ), terminated ( false ), + joined ( false ) { - epicsThreadOpts opts; - epicsThreadOptsDefaults(&opts); + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; opts.stackSize = stackSize; opts.priority = priority; opts.joinable = 1; diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index 694ac94c7..82fa04b5b 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -84,9 +84,8 @@ epicsShareFunc void epicsShareAPI epicsThreadExitMain(void); typedef struct epicsThreadOpts { /** Thread priority in OSI range (cf. epicsThreadPriority*) */ unsigned int priority; - /** Thread stack size, as returned by epicsThreadGetStackSize(). - * - * @warning Do not pass enum epicsThreadStackSizeClass directly! + /** Thread stack size, either in bytes for this architecture or + * an enum epicsThreadStackSizeClass value. */ unsigned int stackSize; /** Should thread be joinable? (default (0) is not joinable). @@ -95,8 +94,14 @@ typedef struct epicsThreadOpts { unsigned int joinable; } epicsThreadOpts; -/** Fill in target specific default values. */ -epicsShareFunc void epicsThreadOptsDefaults(epicsThreadOpts *opts); +/** Default initial values for epicsThreadOpts + * Applications should always use this macro to initialize an epicsThreadOpts + * structure. Additional fields may be added in the future, and the order of + * the fields might also change, thus code that assumes the above definition + * might break if these rules are not followed. + */ +#define EPICS_THREAD_OPTS_INIT { \ + epicsThreadPriorityLow, epicsThreadStackMedium, 0} /** @brief Allocate and start a new OS thread. * @param name A name describing this thread. Appears in various log and error message. diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c index fdc31759f..bdcd8c17e 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.c +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c @@ -152,13 +152,6 @@ epicsThreadGetStackSize (epicsThreadStackSizeClass size) return stackSize; } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 5000, 0}; - -void epicsThreadOptsDefaults(epicsThreadOpts *opts) -{ - *opts = opts_default; -} - /* * Ensure integrity of task variable list */ @@ -315,15 +308,22 @@ epicsThreadCreateOpt ( const char * name, EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ) { + unsigned int stackSize; rtems_id tid; rtems_status_code sc; char c[4]; - unsigned stackSize; - if(!opts) opts = &opts_default; + if (!initialized) + epicsThreadInit(); + + if (!opts) { + static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT; + opts = &opts_default; + } stackSize = opts->stackSize; + if (stackSize <= epicsThreadStackBig) + stackSize = epicsThreadGetStackSize(stackSize); - if (!initialized) epicsThreadInit(); if (stackSize < RTEMS_MINIMUM_STACK_SIZE) { errlogPrintf ("Warning: epicsThreadCreate %s illegal stackSize %d\n", name, stackSize); diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index 6d43e769f..1cfa1ec5c 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -469,13 +469,6 @@ epicsShareFunc unsigned int epicsShareAPI return stackSizeTable[stackSizeClass]; } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1), 0}; - -void epicsThreadOptsDefaults(epicsThreadOpts *opts) -{ - *opts = opts_default; -} - void epicsThreadCleanupWIN32 () { win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal (); @@ -599,6 +592,7 @@ epicsThreadId epicsThreadCreateOpt ( { win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal (); win32ThreadParam * pParmWIN32; + unsigned int stackSize; int osdPriority; DWORD wstat; BOOL bstat; @@ -607,7 +601,13 @@ epicsThreadId epicsThreadCreateOpt ( return NULL; } - if(!opts) opts = &opts_default; + if (!opts) { + static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT; + opts = &opts_default; + } + stackSize = opts->stackSize; + if (stackSize <= epicsThreadStackBig) + stackSize = epicsThreadGetStackSize(stackSize); pParmWIN32 = epicsThreadParmCreate ( pName ); if ( pParmWIN32 == 0 ) { @@ -620,7 +620,7 @@ epicsThreadId epicsThreadCreateOpt ( { unsigned threadId; pParmWIN32->handle = (HANDLE) _beginthreadex ( - 0, opts->stackSize, epicsWin32ThreadEntry, + 0, stackSize, epicsWin32ThreadEntry, pParmWIN32, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, & threadId ); diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index 5ed2cbc56..ea0d2b265 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -469,13 +469,6 @@ epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize (epicsThreadSt return 0; #endif /*_POSIX_THREAD_ATTR_STACKSIZE*/ } - -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, STACK_SIZE(1), 0}; - -void epicsThreadOptsDefaults(epicsThreadOpts *opts) -{ - *opts = opts_default; -} epicsShareFunc void epicsShareAPI epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg) { @@ -519,48 +512,65 @@ epicsShareFunc void epicsShareAPI epicsThreadOnce(epicsThreadOnceId *id, void (* } epicsThreadId -epicsThreadCreateOpt ( - const char * name, +epicsThreadCreateOpt(const char * name, EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ) { + unsigned int stackSize; epicsThreadOSD *pthreadInfo; int status; sigset_t blockAllSig, oldSig; - if(!opts) opts = &opts_default; - epicsThreadInit(); assert(pcommonAttr); + + if (!opts) { + static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT; + opts = &opts_default; + } + stackSize = opts->stackSize; + if (stackSize <= epicsThreadStackBig) + stackSize = epicsThreadGetStackSize(stackSize); + sigfillset(&blockAllSig); - pthread_sigmask(SIG_SETMASK,&blockAllSig,&oldSig); - pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm,opts->joinable); - if(pthreadInfo==0) return 0; + pthread_sigmask(SIG_SETMASK, &blockAllSig, &oldSig); + + pthreadInfo = init_threadInfo(name, opts->priority, stackSize, funptr, + parm, opts->joinable); + if (pthreadInfo==0) + return 0; + pthreadInfo->isEpicsThread = 1; - setSchedulingPolicy(pthreadInfo,SCHED_FIFO); + setSchedulingPolicy(pthreadInfo, SCHED_FIFO); pthreadInfo->isRealTimeScheduled = 1; - status = pthread_create(&pthreadInfo->tid,&pthreadInfo->attr, - start_routine,pthreadInfo); - if(status==EPERM){ + + status = pthread_create(&pthreadInfo->tid, &pthreadInfo->attr, + start_routine, pthreadInfo); + if (status==EPERM) { /* Try again without SCHED_FIFO*/ free_threadInfo(pthreadInfo); - pthreadInfo = init_threadInfo(name,opts->priority,opts->stackSize,funptr,parm,opts->joinable); - if(pthreadInfo==0) return 0; + + pthreadInfo = init_threadInfo(name, opts->priority, stackSize, + funptr, parm, opts->joinable); + if (pthreadInfo==0) + return 0; + pthreadInfo->isEpicsThread = 1; - status = pthread_create(&pthreadInfo->tid,&pthreadInfo->attr, - start_routine,pthreadInfo); + status = pthread_create(&pthreadInfo->tid, &pthreadInfo->attr, + start_routine, pthreadInfo); } - checkStatusOnce(status,"pthread_create"); - if(status) { + checkStatusOnce(status, "pthread_create"); + if (status) { free_threadInfo(pthreadInfo); return 0; } - status = pthread_sigmask(SIG_SETMASK,&oldSig,NULL); - checkStatusOnce(status,"pthread_sigmask"); - if(pthreadInfo->joinable) { + + status = pthread_sigmask(SIG_SETMASK, &oldSig, NULL); + checkStatusOnce(status, "pthread_sigmask"); + if (pthreadInfo->joinable) { /* extra ref for epicsThreadMustJoin() */ epicsAtomicIncrIntT(&pthreadInfo->refcnt); } - return(pthreadInfo); + return pthreadInfo; } /* diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index 43770eab3..d5b859ade 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -150,13 +150,6 @@ unsigned int epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass) return stackSizeTable[stackSizeClass]; } -static const epicsThreadOpts opts_default = {epicsThreadPriorityLow, 4000*ARCH_STACK_FACTOR, 0}; - -void epicsThreadOptsDefaults(epicsThreadOpts *opts) -{ - *opts = opts_default; -} - struct epicsThreadOSD {}; /* Strictly speaking this should be a WIND_TCB, but we only need it to * be able to create an epicsThreadId that is guaranteed never to be @@ -256,20 +249,27 @@ static void createFunction(EPICSTHREADFUNC func, void *parm) epicsThreadId epicsThreadCreateOpt(const char * name, EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts ) { + unsigned int stackSize; int tid; - if (!opts) - opts = &opts_default; - epicsThreadInit(); - if (opts->stackSize < 100) { + + if (!opts) { + static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT; + opts = &opts_default; + } + stackSize = opts->stackSize; + if (stackSize <= epicsThreadStackBig) + stackSize = epicsThreadGetStackSize(stackSize); + + if (stackSize < 100) { errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n", - name, opts->stackSize); + name, stackSize); return 0; } tid = taskCreate((char *)name,getOssPriorityValue(opts->priority), - TASK_FLAGS, opts->stackSize, + TASK_FLAGS, stackSize, (FUNCPTR)createFunction, (int)funptr, (int)parm, 0,0,0,0,0,0,0,0); if (tid == ERROR) { diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index 6785f32ba..d07552d15 100644 --- a/modules/libcom/test/epicsThreadTest.cpp +++ b/modules/libcom/test/epicsThreadTest.cpp @@ -40,7 +40,7 @@ private: }; myThread::myThread(int arg,const char *name) : - thread(*this,name,epicsThreadGetStackSize(epicsThreadStackSmall),50+arg), + thread(*this,name,epicsThreadStackSmall,50+arg), argvalue(0) { argvalue = new int; @@ -132,16 +132,14 @@ void joinTests(void *arg) void testJoining() { - epicsThreadOpts opts; - epicsThreadOptsDefaults(&opts); - opts.priority = 50; - opts.joinable = 1; - + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; epicsEvent finished, trigger; - struct joinStuff stuff = { &opts, &trigger, &finished }; + + opts.priority = 50; + opts.joinable = 1; epicsThreadCreateOpt("parent", &joinTests, &stuff, &opts); // as selfjoin joins itself, we can't. @@ -172,9 +170,8 @@ static void thread(void *arg) static void testOkToBlock() { + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; - epicsThreadOpts opts; - epicsThreadOptsDefaults(&opts); opts.priority = 50; opts.joinable = 1; @@ -190,7 +187,6 @@ static void testOkToBlock() epicsThreadMustJoin(threadA); testOk1(infoA.didSomething); - } From 99be9a86a0bb2a16eac334aa8e1d509e5558cb6c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 2 Jul 2019 17:31:37 -0500 Subject: [PATCH 23/25] Rework EPICS_THREAD_CAN_JOIN RTEMS osdThread.h was missing an extern "C" wrapper. --- modules/libcom/src/osi/epicsThread.h | 5 ++++- modules/libcom/src/osi/os/Linux/osdThread.h | 3 --- modules/libcom/src/osi/os/RTEMS/osdThread.h | 18 +++++++++++++----- modules/libcom/src/osi/os/WIN32/osdThread.h | 8 ++------ modules/libcom/src/osi/os/posix/osdThread.h | 8 ++------ modules/libcom/src/osi/os/vxWorks/osdThread.c | 8 +++----- modules/libcom/src/osi/os/vxWorks/osdThread.h | 13 +++++-------- 7 files changed, 29 insertions(+), 34 deletions(-) diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h index 82fa04b5b..da16a0b25 100644 --- a/modules/libcom/src/osi/epicsThread.h +++ b/modules/libcom/src/osi/epicsThread.h @@ -124,7 +124,10 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate ( epicsShareFunc epicsThreadId epicsShareAPI epicsThreadMustCreate ( const char * name, unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC funptr,void * parm ); -/** Wait for a joinable thread to exit (return from its main function */ + +/* This gets undefined in osdThread.h on VxWorks < 6.9 */ +#define EPICS_THREAD_CAN_JOIN +/** Wait for a joinable thread to exit (return from its main function) */ epicsShareFunc void epicsThreadMustJoin(epicsThreadId id); /** Block the current thread until epicsThreadResume(). */ epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void); diff --git a/modules/libcom/src/osi/os/Linux/osdThread.h b/modules/libcom/src/osi/os/Linux/osdThread.h index 40a837e9f..bb1fdcb0a 100644 --- a/modules/libcom/src/osi/os/Linux/osdThread.h +++ b/modules/libcom/src/osi/os/Linux/osdThread.h @@ -16,9 +16,6 @@ #include "ellLib.h" #include "epicsEvent.h" -/* This target supports joining threads */ -#define EPICS_THREAD_CAN_JOIN (1) - #ifdef __cplusplus extern "C" { #endif diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.h b/modules/libcom/src/osi/os/RTEMS/osdThread.h index 84d579c0f..4eef8c01f 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.h +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.h @@ -3,13 +3,21 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. \*************************************************************************/ -/* This target supports joining threads */ -#define EPICS_THREAD_CAN_JOIN (1) +#ifndef INC_osdThread_H +#define INC_osdThread_H + +#ifdef __cplusplus +extern "C" { +#endif int epicsThreadGetOssPriorityValue(unsigned int osiPriority); +#ifdef __cplusplus +} +#endif + +#endif /* INC_osdThread_H */ diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.h b/modules/libcom/src/osi/os/WIN32/osdThread.h index fe60564d1..69bc364f0 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.h +++ b/modules/libcom/src/osi/os/WIN32/osdThread.h @@ -3,15 +3,11 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. \*************************************************************************/ #ifndef osdThreadh #define osdThreadh -/* This target supports joining threads */ -#define EPICS_THREAD_CAN_JOIN (1) - #endif /* osdThreadh */ diff --git a/modules/libcom/src/osi/os/posix/osdThread.h b/modules/libcom/src/osi/os/posix/osdThread.h index eee0c825d..8fe8f14eb 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.h +++ b/modules/libcom/src/osi/os/posix/osdThread.h @@ -3,9 +3,8 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. \*************************************************************************/ #ifndef osdThreadh #define osdThreadh @@ -16,9 +15,6 @@ #include "ellLib.h" #include "epicsEvent.h" -/* This target supports joining threads */ -#define EPICS_THREAD_CAN_JOIN (1) - #ifdef __cplusplus extern "C" { #endif diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c index d5b859ade..0ed31389f 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.c +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c @@ -34,7 +34,7 @@ #include "vxLib.h" #include "epicsExit.h" -#if EPICS_THREAD_CAN_JOIN +#ifdef EPICS_THREAD_CAN_JOIN /* The implementation of epicsThreadMustJoin() here uses 2 features * of VxWorks that were first introduced in VxWorks 6.9: taskWait(), * and the taskSpareFieldGet/Set routines in taskUtilLib. @@ -188,7 +188,7 @@ void epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg) semGive(epicsThreadOnceMutex); } -#if EPICS_THREAD_CAN_JOIN +#ifdef EPICS_THREAD_CAN_JOIN /* This routine is not static so it appears in the back-trace * of a thread that is waiting to be joined. @@ -286,8 +286,8 @@ epicsThreadId epicsThreadCreateOpt(const char * name, void epicsThreadMustJoin(epicsThreadId id) { +#ifdef EPICS_THREAD_CAN_JOIN const char *fn = "epicsThreadMustJoin"; -#if EPICS_THREAD_CAN_JOIN int tid = (int) id; SEM_ID joinSem; STATUS status; @@ -340,8 +340,6 @@ void epicsThreadMustJoin(epicsThreadId id) } cantProceed(fn); } -#else - cantProceed("%s called when EPICS_THREAD_CAN_JOIN is 0\n", fn); #endif } diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h index 8fa454ce3..15145663b 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.h +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h @@ -3,20 +3,17 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. \*************************************************************************/ + #ifndef osdThreadh #define osdThreadh /* VxWorks 6.9 and later can support joining threads */ -#if (_WRS_VXWORKS_MAJOR > 6) || \ - (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR >= 9) -# define EPICS_THREAD_CAN_JOIN (1) -#else -# define EPICS_THREAD_CAN_JOIN (0) +#if (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR < 9) +#undef EPICS_THREAD_CAN_JOIN #endif #endif /* osdThreadh */ From e5782ae716ea60f721ed0545d60412dcb68db5e5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 2 Jul 2019 17:38:16 -0500 Subject: [PATCH 24/25] Update release notes again, more detail --- documentation/RELEASE_NOTES.html | 95 +++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index b3e266031..203b7a873 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -26,42 +26,87 @@ release.

--> -

VxWorks Minimum Version Requirements

+

VxWorks Minimum Version Recommendation

-

The implementation of the epicsThreadMustJoin() functionality -described below requires facilities that were added to the OS in VxWorks 6.9, so -that is now the oldest version which this release of EPICS can be built -against.

+

The implementation of the epicsThreadMustJoin() feature +described below uses facilities that were added to VxWorks in version 6.9. When +built against an older version of VxWorks the join functionality will not be +available and calls to epicsThreadMustJoin() will return +immediately. In this case the epicsThread.h header will not define the C macro +EPICS_THREAD_CAN_JOIN to allow alternate code to be provided for +these targets. The IOC's use of the join feature has been designed to work for +either situation.

-

Add epicsThreadCreateOpt() and epicsThreadMustJoin()

-

epicsThreadCreateOpt() is an alternative to epicsThreadCreate() which -passes some arguments via a structure (struct epicsThreadOpts). -This epicsThreadOpts* may be NULL to use target specific -defaults. Caller wishing to provide thread options should first call -epicsThreadOptsDefaults() to fill in the defaults.

+

New and modified epicsThread APIs

+ +

epicsThreadCreateOpt()

+ +

A new routine epicsThreadCreateOpt() is an alternative to +epicsThreadCreate() which takes some arguments via a structure +(struct epicsThreadOpts) to allow for future extensions.

+ +
+typedef struct epicsThreadOpts {
+    unsigned int priority;
+    unsigned int stackSize;
+    unsigned int joinable;
+} epicsThreadOpts;
+#define EPICS_THREAD_OPTS_INIT { \
+    epicsThreadPriorityLow, epicsThreadStackMedium, 0}
+
+epicsThreadId epicsThreadCreateOpt(const char * name,
+    EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts);
+
+ +

The final opts parameter may be NULL to use the +default values of thread priority (low) and stack size (medium). Callers wishing +to provide alternative settings for these thread options or to create a joinable +thread (see below) should create and pass in an epicsThreadOpts +structure as shown below. Always initialize one of these structures using the +EPICS_THREAD_OPTS_INIT macro to ensure that any additional fields +that get added in the future are set to their default values.

 void startitup(void) {
-    epicsThreadOpts opts;
-    epicsThreadOptsDefaults(&opts);
-    opts.priority = epicsThreadPriorityMedium;
+    epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
+    epicsThreadId tid;
 
-    ... = epicsThreadCreateOpt("my thread", &threadMain, NULL, &opts);
+    opts.priority = epicsThreadPriorityMedium;
+    tid = epicsThreadCreateOpt("my thread", &threadMain, NULL, &opts);
 }
 
-

If the new epicsThreadOpts::joinable option flag is set (not the default), -then epicsThreadMustJoin() must be called with that -thread's epicsThreadId when/after the thread exits, to free up thread resources. -This function will block until the thread's main function has returned, after -which the epicsThreadId will no longer be valid.

+

C or C++ Code that also needs to build on earlier versions of Base can use +#ifdef EPICS_THREAD_OPTS_INIT to determine whether the +epicsThreadCreateOpt() API is available on this Base version.

+ +

Thread stack sizes

+ +

The stackSize member of the epicsThreadOpts +structure and the equivalent parameters to the epicsThreadCreate() +and epicsThreadMustCreate() routines can now be passed either one +of the epicsThreadStackSizeClass enum values or a value returned +from the epicsThreadGetStackSize() routine.

+ +

epicsThreadMustJoin()

+ +

If the new joinable flag of an epicsThreadOpts +structure is non-zero (the default value is zero), the new API routine +epicsThreadMustJoin() must be called with the thread's +epicsThreadId when/after the thread exits, to free up thread +resources. This function will block until the thread's main function has +returned, allowing the parent to wait for its child thread. The child's +epicsThreadId will no longer be valid and should not be used after +the epicsThreadMustJoin() routine returns.

+ +

A thread that was originally created with its joinable flag set may itself +call epicsThreadMustJoin(), passing in its own epicsThreadId. This +marks the thread as no longer being joinable, so it will then free the thread +resources itself when its main function returns. The epicsThreadId +of a thread that is not joinable gets invalidated as soon as its main function +returns.

-

A thread which was created with the joinable flag set may itself call -epicsThreadMustJoin() passing its own epicsThreadId. This marks the -thread as no longer being joinable, so it will then free the thread resources -itself when its main function returns. The epicsThreadId for a thread that is -not joinable gets invalidated as soon as its main function returns.

Launchpad Bugs

From b4ee452c4d39424f9b5a8910d85776eb979e4211 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 2 Jul 2019 18:17:47 -0500 Subject: [PATCH 25/25] Test that a join actually delays the parent --- modules/libcom/test/epicsThreadTest.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index d07552d15..4796438a1 100644 --- a/modules/libcom/test/epicsThreadTest.cpp +++ b/modules/libcom/test/epicsThreadTest.cpp @@ -102,6 +102,11 @@ void dowait(void *arg) epicsThreadSleep(0.1); } +void dodelay(void *arg) +{ + epicsThreadSleep(2.0); +} + void joinTests(void *arg) { struct joinStuff *stuff = (struct joinStuff *) arg; @@ -118,6 +123,20 @@ void joinTests(void *arg) stuff->trigger->signal(); epicsThreadMustJoin(tid); + // Parent gets delayed until task finishes + epicsTime start, end; + start = epicsTime::getCurrent(); + tid = epicsThreadCreateOpt("delay", + &dodelay, 0, stuff->opts); + epicsThreadMustJoin(tid); + end = epicsTime::getCurrent(); + double duration = end - start; +#ifndef EPICS_THREAD_CAN_JOIN + testTodoBegin("Thread join doesn't work"); +#endif + testOk(duration > 1.0, "Join delayed parent (%g seconds)", duration); + testTodoEnd(); + // This is a no-op epicsThreadId self = epicsThreadGetIdSelf(); epicsThreadMustJoin(self); @@ -192,7 +211,7 @@ static void testOkToBlock() MAIN(epicsThreadTest) { - testPlan(12); + testPlan(13); unsigned int ncpus = epicsThreadGetCPUs(); testDiag("System has %u CPUs", ncpus);