diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index 10910080d..faf6e56ba 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -30,6 +30,76 @@ release.

--> +

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 = EPICS_THREAD_OPTS_INIT;
+    epicsThreadId tid;
+
+    opts.priority = epicsThreadPriorityMedium;
+    tid = epicsThreadCreateOpt("my thread", &threadMain, NULL, &opts);
+}
+
+ +

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.

+ +

Non-VME RTEMS targets now define pdevLibVME

Previously IOC executables that made calls to devLib routines would fail to diff --git a/modules/database/src/ioc/as/asCa.c b/modules/database/src/ioc/as/asCa.c index d0180448b..21bb47f5f 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 = EPICS_THREAD_OPTS_INIT; + + 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);} diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c index 843fbfc0c..45e178cae 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 = EPICS_THREAD_OPTS_INIT; + + 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); } diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 77b12de8d..5e0f4db7c 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -84,7 +84,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 */ @@ -123,8 +122,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 ) { @@ -266,10 +263,6 @@ dbEventCtx db_init_events (void) { struct event_user * evUser; - if (!stopSync) { - stopSync = epicsMutexMustCreate(); - } - if (!dbevEventUserFreeList) { freeListInitPvt(&dbevEventUserFreeList, sizeof(struct event_user),8); @@ -293,9 +286,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) @@ -310,9 +300,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; @@ -327,8 +314,6 @@ fail: epicsEventDestroy (evUser->ppendsem); if(evUser->pflush_sem) epicsEventDestroy (evUser->pflush_sem); - if(evUser->pexitsem) - epicsEventDestroy (evUser->pexitsem); freeListFree(dbevEventUserFreeList,evUser); return NULL; } @@ -349,7 +334,6 @@ epicsShareFunc void db_cleanup_events(void) dbevFieldLogFreeList = NULL; } - /* intentionally leak stopSync to avoid possible shutdown races */ /* * DB_CLOSE_EVENTS() * @@ -371,30 +355,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 */ } /* @@ -1074,17 +1043,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; } @@ -1096,6 +1062,11 @@ int db_start_events ( void *init_func_arg, unsigned osiPriority ) { struct event_user * const evUser = (struct event_user *) ctx; + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + + opts.stackSize = epicsThreadGetStackSize(epicsThreadStackMedium); + opts.priority = osiPriority; + opts.joinable = 1; epicsMutexMustLock ( evUser->lock ); @@ -1113,15 +1084,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; } 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(); } 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/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp index 892d73de0..92f833847 100644 --- a/modules/libcom/src/osi/epicsThread.cpp +++ b/modules/libcom/src/osi/epicsThread.cpp @@ -31,6 +31,18 @@ using namespace std; +epicsThreadId epicsShareAPI epicsThreadCreate ( + const char * name, unsigned int priority, unsigned int stackSize, + EPICSTHREADFUNC funptr,void * parm ) +{ + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + opts.priority = priority; + opts.stackSize = stackSize; + opts.joinable = 0; + + return epicsThreadCreateOpt(name, funptr, parm, &opts); +} + epicsThreadRunable::~epicsThreadRunable () {} void epicsThreadRunable::run () {} void epicsThreadRunable::show ( unsigned int ) const {} @@ -141,6 +153,13 @@ bool epicsThread::exitWait ( const double delay ) throw () if ( this->pThreadDestroyed ) { *this->pThreadDestroyed = true; } + if(!joined) { + { + epicsGuard < epicsMutex > guard ( this->mutex ); + joined = true; + } + epicsThreadMustJoin(this->id); + } return true; } epicsTime exitWaitBegin = epicsTime::getCurrent (); @@ -154,6 +173,12 @@ bool epicsThread::exitWait ( const double delay ) throw () epicsTime current = epicsTime::getCurrent (); exitWaitElapsed = current - exitWaitBegin; } + if(this->terminated && !joined) { + joined = true; + + epicsGuardRelease < epicsMutex > unguard ( guard ); + epicsThreadMustJoin(this->id); + } } catch ( std :: exception & except ) { errlogPrintf ( @@ -177,11 +202,18 @@ epicsThread::epicsThread ( epicsThreadRunable & runableIn, const char * pName, unsigned stackSize, unsigned priority ) : runable ( runableIn ), id ( 0 ), pThreadDestroyed ( 0 ), - begin ( false ), cancel ( false ), terminated ( false ) + begin ( false ), cancel ( false ), terminated ( false ), + joined ( false ) { - this->id = epicsThreadCreate ( - pName, priority, stackSize, epicsThreadCallEntryPoint, - static_cast < void * > ( this ) ); + epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; + 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 84b2c4788..da16a0b25 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,46 +80,140 @@ 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; + /** 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). + * If joinable=1, then epicsThreadMustJoin() must be called for cleanup thread resources. + */ + unsigned int joinable; +} epicsThreadOpts; + +/** 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. + * @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 ); + EPICSTHREADFUNC funptr,void * parm ); + +/* 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); +/** 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); @@ -115,10 +224,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 @@ -130,33 +246,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 */ @@ -178,6 +325,7 @@ private: bool begin; bool cancel; bool terminated; + bool joined; bool beginWait () throw (); epicsThread ( const epicsThread & ); diff --git a/modules/libcom/src/osi/os/Linux/osdThread.h b/modules/libcom/src/osi/os/Linux/osdThread.h index 7d2a4868d..bb1fdcb0a 100644 --- a/modules/libcom/src/osi/os/Linux/osdThread.h +++ b/modules/libcom/src/osi/os/Linux/osdThread.h @@ -22,6 +22,7 @@ extern "C" { typedef struct epicsThreadOSD { ELLNODE node; + int refcnt; pthread_t tid; pid_t lwpId; pthread_attr_t attr; @@ -35,6 +36,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 769e95820..bdcd8c17e 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; @@ -163,6 +167,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. @@ -183,9 +203,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); } @@ -196,21 +219,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 (); @@ -222,10 +258,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; } /* @@ -247,7 +287,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, @@ -263,15 +304,26 @@ 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 ) { + unsigned int stackSize; rtems_id tid; rtems_status_code sc; char c[4]; - if (!initialized) epicsThreadInit(); + 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 (stackSize < RTEMS_MINIMUM_STACK_SIZE) { errlogPrintf ("Warning: epicsThreadCreate %s illegal stackSize %d\n", name, stackSize); @@ -279,7 +331,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, @@ -289,7 +341,13 @@ epicsThreadCreate (const char *name, 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; } @@ -305,6 +363,51 @@ threadMustCreate (const char *name, return tid; } +void epicsThreadMustJoin(epicsThreadId id) +{ + rtems_id target_tid = (rtems_id)id, self_tid; + struct taskVar *v = 0; + + rtems_task_ident (RTEMS_SELF, 0, &self_tid); + + { + uint32_t note; + 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 || !v->joinable) { + if(epicsThreadGetIdSelf()==id) { + 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 + * '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) { + /* 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("epicsThreadMustJoin('%s') -> %s\n", v->name, rtems_status_text(sc)); + } + } + + v->joinable = 0; + 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 4451f845a..4eef8c01f 100644 --- a/modules/libcom/src/osi/os/RTEMS/osdThread.h +++ b/modules/libcom/src/osi/os/RTEMS/osdThread.h @@ -3,10 +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. \*************************************************************************/ +#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.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index 8cdb4a3f4..1cfa1ec5c 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 ); @@ -526,6 +531,7 @@ static win32ThreadParam * epicsThreadParmCreate ( const char *pName ) pParmWIN32->pName = (char *) ( pParmWIN32 + 1 ); strcpy ( pParmWIN32->pName, pName ); pParmWIN32->isSuspended = 0; + epicsAtomicIncrIntT(&pParmWIN32->refcnt); } return pParmWIN32; } @@ -579,11 +585,14 @@ 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; + unsigned int stackSize; int osdPriority; DWORD wstat; BOOL bstat; @@ -592,18 +601,26 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName, return NULL; } + 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 ) { 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, stackSize, epicsWin32ThreadEntry, pParmWIN32, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, & threadId ); @@ -615,7 +632,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 ); @@ -637,9 +654,48 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName, return NULL; } + if(opts->joinable) { + pParmWIN32->joinable = 1; + epicsAtomicIncrIntT(&pParmWIN32->refcnt); + } + return ( epicsThreadId ) pParmWIN32; } +void epicsThreadMustJoin(epicsThreadId id) +{ + win32ThreadParam * pParmWIN32 = id; + + if(!id) { + /* no-op */ + } else if(!pParmWIN32->joinable) { + 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) { + DWORD status = WaitForSingleObject(pParmWIN32->handle, INFINITE); + if(status != WAIT_OBJECT_0) { + /* TODO: signal error? */ + } + + pParmWIN32->joinable = 0; + epicsParmCleanupWIN32(pParmWIN32); + } else { + /* join self silently does nothing */ + pParmWIN32->joinable = 0; + epicsParmCleanupWIN32(pParmWIN32); + } +} + /* * epicsThreadSuspendSelf () */ diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.h b/modules/libcom/src/osi/os/WIN32/osdThread.h index 136e96bf4..69bc364f0 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.h +++ b/modules/libcom/src/osi/os/WIN32/osdThread.h @@ -3,13 +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 - #endif /* osdThreadh */ diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index e3e0fe3cf..a13d3876c 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"); @@ -445,15 +453,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 (stackSizeClassstackSize; + if (stackSize <= epicsThreadStackBig) + stackSize = epicsThreadGetStackSize(stackSize); + sigfillset(&blockAllSig); - pthread_sigmask(SIG_SETMASK,&blockAllSig,&oldSig); - pthreadInfo = init_threadInfo(name,priority,stackSize,funptr,parm); - 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,priority,stackSize,funptr,parm); - 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"); - return(pthreadInfo); + + status = pthread_sigmask(SIG_SETMASK, &oldSig, NULL); + checkStatusOnce(status, "pthread_sigmask"); + if (pthreadInfo->joinable) { + /* extra ref for epicsThreadMustJoin() */ + epicsAtomicIncrIntT(&pthreadInfo->refcnt); + } + return pthreadInfo; } /* @@ -584,7 +623,40 @@ static epicsThreadOSD *createImplicit(void) } return pthreadInfo; } - + +void epicsThreadMustJoin(epicsThreadId id) +{ + void *ret = NULL; + int status; + + if(!id) { + return; + } else if(!id->joinable) { + 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; + } + + 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"); + id->joinable = 0; + 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..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 @@ -22,6 +21,7 @@ extern "C" { typedef struct epicsThreadOSD { ELLNODE node; + int refcnt; pthread_t tid; pthread_attr_t attr; struct sched_param schedParam; @@ -34,6 +34,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 ce01ea609..0ed31389f 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" +#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. + */ + #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; @@ -170,19 +187,58 @@ void epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg) } semGive(epicsThreadOnceMutex); } - + +#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. + */ +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 @@ -190,27 +246,101 @@ 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 ) { + unsigned int stackSize; int tid; epicsThreadInit(); - if(stackSize<100) { - errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n",name,stackSize); - return(0); + + if (!opts) { + static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT; + opts = &opts_default; } - tid = taskSpawn((char *)name,getOssPriorityValue(priority), + stackSize = opts->stackSize; + if (stackSize <= epicsThreadStackBig) + stackSize = epicsThreadGetStackSize(stackSize); + + if (stackSize < 100) { + errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n", + name, stackSize); + return 0; + } + + tid = taskCreate((char *)name,getOssPriorityValue(opts->priority), TASK_FLAGS, 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) +{ +#ifdef EPICS_THREAD_CAN_JOIN + const char *fn = "epicsThreadMustJoin"; + int tid = (int) id; + SEM_ID joinSem; + STATUS status; + + 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); + } +#endif } void epicsThreadSuspendSelf() diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h index 2ee9f2d46..15145663b 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdThread.h +++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h @@ -3,11 +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_MINOR < 9) +#undef EPICS_THREAD_CAN_JOIN +#endif + #endif /* osdThreadh */ diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp index eb26cc8bf..4796438a1 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 { @@ -37,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; @@ -52,7 +55,7 @@ void myThread::run() startEvt.signal(); int *pset = argvalue; privateKey.set(argvalue); - epicsThreadSleep(2.0); + int *pget = privateKey.get(); testOk1(pget == pset); @@ -60,34 +63,8 @@ void myThread::run() testOk1(thread.getPriority() == epicsThreadGetPriority(self)); } - -typedef struct info { - int isOkToBlock; -} info; - -extern "C" { -static void thread(void *arg) +void testMyThread() { - info *pinfo = (info *)arg; - - epicsThreadSetOkToBlock(pinfo->isOkToBlock); - epicsThreadSleep(1.0); - - testOk(epicsThreadIsOkToBlock() == pinfo->isOkToBlock, - "%s epicsThreadIsOkToBlock() = %d", - epicsThreadGetNameSelf(), pinfo->isOkToBlock); - epicsThreadSleep(0.1); -} -} - - -MAIN(epicsThreadTest) -{ - testPlan(9); - - unsigned int ncpus = epicsThreadGetCPUs(); - testDiag("System has %u CPUs", ncpus); - testOk1(ncpus > 0); const int ntasks = 3; myThread *myThreads[ntasks]; @@ -107,16 +84,149 @@ MAIN(epicsThreadTest) myThreads[i]->thread.exitWait(); delete myThreads[i]; } +} - unsigned int stackSize = epicsThreadGetStackSize(epicsThreadStackSmall); +struct joinStuff { + epicsThreadOpts *opts; + epicsEvent *trigger; + epicsEvent *finished; +}; - info infoA = {0}; - epicsThreadCreate("threadA", 50, stackSize, thread, &infoA); +void donothing(void *arg) +{} - info infoB = {1}; - epicsThreadCreate("threadB", 50, stackSize, thread, &infoB); +void dowait(void *arg) +{ + epicsEvent *trigger = (epicsEvent *) arg; + trigger->wait(); + epicsThreadSleep(0.1); +} +void dodelay(void *arg) +{ epicsThreadSleep(2.0); +} + +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); + + // 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); + + // This is a no-op as well, except for a warning. + eltc(0); + epicsThreadMustJoin(self); + eltc(1); + + stuff->finished->signal(); +} + +void testJoining() +{ + 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. + testOk(finished.wait(10.0), "Join tests completed"); +} + +} // namespace + +typedef struct info { + int isOkToBlock; + int didSomething; +} info; + +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 = EPICS_THREAD_OPTS_INIT; + + opts.priority = 50; + opts.joinable = 1; + + info infoA = {0, 0}; + epicsThreadId threadA = epicsThreadCreateOpt("threadA", thread, &infoA, &opts); + + info infoB = {1, 0}; + epicsThreadId threadB = epicsThreadCreateOpt("threadB", thread, &infoB, &opts); + + // join B first to better our chance of detecting if it never runs. + epicsThreadMustJoin(threadB); + testOk1(infoB.didSomething); + + epicsThreadMustJoin(threadA); + testOk1(infoA.didSomething); +} + + +MAIN(epicsThreadTest) +{ + testPlan(13); + + unsigned int ncpus = epicsThreadGetCPUs(); + testDiag("System has %u CPUs", ncpus); + testOk1(ncpus > 0); + testDiag("main() thread %p", epicsThreadGetIdSelf()); + + testJoining(); // Do this first, ~epicsThread() uses it... + testMyThread(); + 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(); }