Merge ~epics-core/epics-base/+git/Com:thread-join into ~epics-core/epics-base/+git/epics-base:7.0

Proposed by mdavidsaver
Status: Merged
Approved by: Andrew Johnson
Approved revision: b4ee452c4d39424f9b5a8910d85776eb979e4211
Merged at revision: 75477b5e9ce671c5d90f0ab21bf38a10e246afee
Proposed branch: ~epics-core/epics-base/+git/Com:thread-join
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Diff against target: 1953 lines (+925/-207) (has conflicts)
18 files modified
documentation/RELEASE_NOTES.html (+84/-0)
modules/database/src/ioc/as/asCa.c (+13/-8)
modules/database/src/ioc/db/dbCa.c (+11/-3)
modules/database/src/ioc/db/dbEvent.c (+18/-50)
modules/database/test/ioc/db/dbChArrTest.cpp (+12/-29)
modules/libcom/RTEMS/rtems_config.c (+1/-0)
modules/libcom/src/osi/epicsThread.cpp (+36/-4)
modules/libcom/src/osi/epicsThread.h (+152/-4)
modules/libcom/src/osi/os/Linux/osdThread.h (+2/-0)
modules/libcom/src/osi/os/RTEMS/osdThread.c (+119/-16)
modules/libcom/src/osi/os/RTEMS/osdThread.h (+14/-3)
modules/libcom/src/osi/os/WIN32/osdThread.c (+61/-5)
modules/libcom/src/osi/os/WIN32/osdThread.h (+2/-4)
modules/libcom/src/osi/os/posix/osdThread.c (+100/-28)
modules/libcom/src/osi/os/posix/osdThread.h (+4/-3)
modules/libcom/src/osi/os/vxWorks/osdThread.c (+145/-15)
modules/libcom/src/osi/os/vxWorks/osdThread.h (+9/-3)
modules/libcom/test/epicsThreadTest.cpp (+142/-32)
Conflict in documentation/RELEASE_NOTES.html
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
mdavidsaver Approve
Review via email: mp+361379@code.launchpad.net

Description of the change

Add epicsThreadJoin() to allow waiting for thread completion. Threads must be explicitly created as join-able. The default is not join-able.

Add epicsThreadCreateOpt() which accepts arguments as a structure for expandability.

The epicsThread class is modified to use epicsThreadJoin() automatically.

Implemented for all targets except vxWorks where epicsThreadJoin() is a no-op and EPICS_THREAD_CAN_JOIN==0.

To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote :

I'm going to need persuading why we want epicsThreadJoin() if it isn't going to be available on all platforms. I am concerned that by providing it, we allow people to write code that won't work without it, making that code inherently incompatible with the targets that don't support it.

Is there any particular reason that it can't be implemented on VxWorks, or is it just that you don't have the time and access? Doing so might be a good project to give Dirk, after adding test code to confirm that the implementation works.

If we do allow it on a subset of platforms, it would be better for VxWorks to not provide the routine at all (including declaring no function prototype for it — this is important since we can't detect someone calling a non-existent routine until boot/load time with VxWorks). That way any code that tries to call it despite the EPICS_THREAD_CAN_JOIN==0 will get a build-time error on VxWorks targets instead of them failing weirdly at runtime because the original author didn't think anyone would ever want to run this code on a platform without it.

review: Needs Information
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> Is there any particular reason that it can't be implemented on VxWorks, or is it just that you don't have the time and access?

I made it work for RTEMS, so I see no reason why it couldn't be made to work for vxWorks as well.

> If we do allow it on a subset of platforms

I would rather support all platforms.

Revision history for this message
Andrew Johnson (anj) wrote :

I noticed the other day that VxWorks 6.9 added this API:

    STATUS taskWait(TASK_ID taskId, _Vx_ticks_t timeout);

This behaves like pthread_join(), but without copying the thread's exit status to the caller.

Should the epicsThreadJoin() API take a timeout argument? That seems like a useful thing which could be hard or impossible to implement in portable application code, although I haven't looked too closely at how easy it would be to implement for the other OSs (RTEMS looks easy though). Adding this would also require adding a return status so the application knows what happened. If the routine returned a status value we could avoid the need to call cantProceed(). We could provide an epicsThreadMustJoin() API which does the cantProceed() thing...

Shouldn't the C++ class also have a join() method? The destructor acts as one so maybe it isn't essential, but there's no way to provide it with timeout value or get errors back.

I see that the epicsThread class always sets opts.joinable=1 even when the underlying OS defines EPICS_THREAD_CAN_JOIN (0) and only provides an empty epicsThreadJoin() routine. Is that what you implemented in the "epicsThread fix join" commit 149ab118?

Looking at the Posix implementation, a joinable thread that calls epicsThreadJoin(epicsThreadGetIdSelf()) should become unjoinable, but the code doesn't reset the id->joinable flag despite calling pthread_detach(). I didn't check the others for similar issues.

Do you have any test code for this? I just created an implementation for VxWorks 6.9 and later but it may be too simple, so adding some tests to epicsThreadTest.cpp would be helpful.

Will eventually need documentation and a Release Notes entry.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> I noticed the other day that VxWorks 6.9 added this API:

8-()

> Should the epicsThreadJoin() API take a timeout argument?

I would say no. Given the nature of threads, join is only useful if the caller has already done something to ensure the thread will (eventually) return. If there is some doubt about this, then it should be handled at this earlier phase.

Otherwise it seems a bit like allowing free() to timeout.

> Shouldn't the C++ class also have a join() method?

I suppose this could be added. I was thinking first of a non-invasive change.

https://en.cppreference.com/w/cpp/thread/thread/join

> EPICS_THREAD_CAN_JOIN

This will go away now.

> epicsThreadJoin(epicsThreadGetIdSelf()) should become unjoinable

I'll take a look.

> adding some tests to epicsThreadTest.cpp would be helpful

Any thoughts on what to test? I couldn't think of any meaningful unittest to see if resources have actually been freed. Mostly I added prints while developing (especially for RTEMS), and ran epicsThreadTest through valgrind on Linux.

Revision history for this message
Andrew Johnson (anj) wrote :

I was thinking that a timeout would at least allow unit test programs to call testAbort() in the event that their threads don't die due to some other bug — this API might turn out to be more useful in unit tests than inside the IOC or other applications, and this could simplify making them more robust against not exiting properly.

I'm starting to think that calling cantProceed() instead of returning an error status might be a mistake, and as I suggested we can always add an epicsThreadMustJoin() routine for those applications that want that behavior. You have already had to change some of our older APIs that only returned void, it would be good to not make that mistake again.

VxWorks 6.8 and earlier still define EPICS_THREAD_CAN_JOIN (0)...

I would start with checks of the basic functionality, and that things the APIs are supposed to reject get rejected (such as self-calling epicsThreadJoin(epicsThreadGetIdSelf()) or on an unjoinable thread; Posix says you can't call pthread_detach() twice, etc. I agree that you probably can't check for resources being freed, but you could check timeout functionality...

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

> Should the epicsThreadJoin() API take a timeout argument?
Definitely no.

> Given the nature of threads, join is only useful if the caller has already done something to ensure the thread will (eventually) return.
I agree. We might want to spend some time making it easier for developers to get the "signal the thread to shutdown" part right. Maybe by providing a good example in the Application Developer's Guide?

> Otherwise it seems a bit like allowing free() to timeout.
It's actually worse: Not freeing something leads to a memory leak (usually a minor problem) but not joining a thread might result in data races which takes us straight to undefined behavior land.

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

I would argue that a stuck test exposes a problem the same way "FAILED" does. The same goes for threads: A thread that doesn't join usually points to a thread function that got stuck. If you want to ignore this problem temporarily you might want to disable/exclude the corresponding test.

Revision history for this message
Andrew Johnson (anj) wrote :

Disabling individual tests on the specific architectures where they fail isn't something we have done in the past. I'm not saying it can't be done, but that merely removes the test lockups from ever being seen. When tests fail to exit that mostly only affects me because I have to see that they've been running for hours and kill that job on the APS Jenkins server.

I would like to make it easier for test authors to build safeguards like timeouts into their code. If a test never fails for an author they aren't as likely to want to work on fixing the actual problem, and adding a timeout is an easy workaround that would save work for me. A test program will be reported as failing if it times out, whereas if I have to kill the job the test results don't get analyzed at all so the author doesn't see the problem (Jenkins doesn't send email when I kill a job).

In a perfect world I might agree that epicsThreadJoin() doesn't need a timeout, but it could make life a little easier in this world IMHO.

If it's too hard to implement a timeout on Posix I would accept that as a reason for not adding it, but I still think that epicsThreadJoin() should return a status, and epicsThreadMustJoin() should be added with the call to cantProceed() — this has been our practice in other APIs e.g. callocMustSucceed(), epicsMutexMustCreate(), epicsMutexMustLock(), epicsEventMustWait(), epicsEventMustTrigger(), epicsSpinMustCreate().

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

Keep in mind that we don't need threads to shoot ourselves in the foot (I can just as well call something equivalent to "while (true)" from a test directly) but I have to admit that multithreading makes it easier ;-)

The problem of CI runs that don't complete is bigger than joining threads. But there is a cure: https://plugins.jenkins.io/build-timeout

BTW: In Google Test I get a yellow "DISABLED" instead of a green "PASS"/a red "FAIL" when I prefix the name of a test with "DISABLE". This makes it hard to forget a disabled test.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> But there is a cure: https://plugins.jenkins.io/build-timeout

I agree an overall timeout would be a better way to solve the Bothering Andrew part of this problem.

If Jenkins can't be made to work, then maybe makeTestfile.pl could be taught about SIGALRM.

> If it's too hard to implement a timeout on Posix I would accept that as a reason for not adding it

The standard pthread_join() doesn't have a timeout. A quick search finds pthread_tryjoin_np(), which is a glibc extension.

> that epicsThreadJoin() should return a status

What status? How would a caller react?

Part of my reasoning for not having a return value is that by the time a join is performed, there often isn't anything sensible to be done.

eg. I regard epicsMutexLock() returning as a mis-feature. There is nothing sensible which user code can do with a epicsMutexLockError. So why offer up the option? Seems an invitation to ignore a serious error.

Revision history for this message
Andrew Johnson (anj) wrote :

The jenkins "Build Timeout" plugin wouldn't help much, the documentation says:

  Once the timeout is reached, Jenkins behaves as if an invisible hand has
  clicked the "abort build" button.

So as I wrote above Jenkins won't send out any emails as a result, and now nobody knows that the job failed.

The problem with implementing a watchdog timeout in the wrapper script is that the timeout period really should vary with the specific test and the machine it's running on. As Lewis Muir pointed out, the code asking for the join knows best how long to wait before giving up, and also what to do if it fails.

The epicsThread class provides an exitWait(double seconds) method which is equivalent to join with a timeout. Even Python's Thread.join() method has a timeout parameter.

A return status value from epicsThreadJoin() would provide the same kinds of information to the caller that pthread_join() and taskWait() return: "Not a joinable thread", "Cannot wait on self", "Invalid thread ID", and even "Timeout". A fundamental library routine shouldn't dictate that the only way to handle errors is for it to ignore them or call cantProceed(); a unit test program at least should be able to call testAbort() instead. Just step back a moment and look at your 3 implementations of epicsThreadJoin() — a significant proportion of the code in them is just handling error conditions.

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

The problem I have with this is that

// ...
t1.joinWithTimeout(timeout);
// read something modified by t1

is a very common pattern. This is safe because join() acts as a barrier in terms of the memory model. If we proceed without ensuring the thread has shut down we need to ensure we never read/write anything that might be written/read by the thread because we can't be sure t1 has shut down. Otherwise we might have a data race (in other words: anything can happen).

Simple example:

void foo(atomic<bool>& terminate, int& shared) {
  while (!terminate)
    shared = ...;
}

{
  atomic<bool> terminate = false;
  int shared;
  thread t1(foo, terminate, shared);
  // ...
  terminate = true;
  t1.joinWithTimeout(timeout);
} // shared goes out of scope - but foo() might keep writing to it!

It's hard to use this correctly. Should you really implement a joinWithTimeout() I think it should come with a big fat warning in the documentation. I would certainly stay away from doing anything beyond calling "exit" if join times out since I'm pretty sure I wouldn't get it right.

P.S.: I like the idea of leveraging SIGALRM.

Revision history for this message
J. Lewis Muir (jlmuir) wrote :

(Hmm, my email reply to the special <email address hidden> email
address did not seem to generate a comment here, so I'm adding the
comment again via the web UI.)

> I would like to make it easier for test authors to build safeguards
> like timeouts into their code.

Agreed. The test case can know that a join should succeed within some
amount of time, and if it doesn't, it can fail the test explicitly with
a message saying exactly what went wrong. This is better than a test
that just hangs.

I think it also has value in production code for a similar reason: if
the caller knows that the join should succeed within a certain amount
of time, it allows the caller to handle the case where it doesn't. It
could log an error message, it could abort, it could do both, it could
try again, it could tell the thread to stop and then try again, etc.

Revision history for this message
J. Lewis Muir (jlmuir) wrote :

> It's hard to use this correctly. Should you really implement a
> joinWithTimeout() I think it should come with a big fat warning in the
> documentation. I would certainly stay away from doing anything beyond calling
> "exit" if join times out since I'm pretty sure I wouldn't get it right.

I don't understand the concern here. If you're dealing with threads, you have to understand the memory model; that's a given. There are a practically infinite number of ways I can write wrong code with this API as well as the entire existing EPICS Base API.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

I like using clear names to distinguish stuff.
"tryjoin" (that Michael found to be a pthread extension) is a good example.

// ...
ret = t1.tryJoin(timeout);
// read something modified by t1

makes it pretty obvious that you should check the returned status before trying to read stuff that t1 should have modified.

Revision history for this message
Andrew Johnson (anj) wrote :

The name epicsThreadMustJoin() would tell someone who knows our other API names that the routine will call cantProceed() if it fails.

I would expect a routine called epicsThreadTryJoin() to be functionally equivalent to the glibc extension pthread_tryjoin_np() that Michael found. It should also be equivalent to passing in a timeout of zero to a timed version.

Might pthreads allow us to write our own equivalent to pthread_timedjoin_np() using waitpid()? I'm hoping for something that might also work on RTEMS-5, MacOS and non-glibc Linux targets...

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Ok, I think we are getting side tracked. I will renamed epicsThreadJoin() -> epicsThreadMustJoin(). However, I leave epicsThreadTryJoin() as future work from someone who wants to do it, and can find a portable way. I don't want this to hold up merging.

wrt. epicsThreadMustJoin() returning an error. The one case I can see which could be indicated in a race free way is self join. This is also a case which a caller can easily check and handle beforehand. So I don't see this as sufficient motivation.

wrt. clearing joinable. I'll change this so that trying to self join twice will be defined. However, other cases of double join, or join of unjoinable, will remain undefined (aka. race to crash).

wrt. vxWorks and taskWait(). I can't find any documentation for this. How does it handled a self wait? For that matter, how does it handle waiting for a thread which might already have returned? Does vxWorks have a notion of (not) detached threads?

wrt. Testing. I'll extend epicsThreadTest with at least an explicit test of self join.

review: Needs Fixing
Revision history for this message
Andrew Johnson (anj) wrote :

taskWait( )

NAME

taskWait( ) - wait for task to terminate

SYNOPSIS

STATUS taskWait
    (
    TASK_ID tid, /* task id */
    _Vx_ticks_t timeout /* timeout */
    )

DESCRIPTION

This routine will wait/pend until a task has been terminated.

WARNING

This routine may not be used from interrupt level

SMP CONSIDERATIONS

This API is spinlock and intCpuLock restricted. These restrictions are not enforced by the implementation so it is the responsibility of the caller to ensure they are complied with. Future implementations may enforce these restrictions.

RETURNS

OK when timeout or done waiting for task to be destroyed, ERROR on
         failure

ERRNO

S_intLib_NOT_ISR_CALLABLE
    Routine is not callable from an ISR.

S_taskLib_ILLEGAL_OPERATION
    Cannot wait on self

S_objLib_OBJ_ID_ERROR
    Invalid task ID

I think it may also set errno to one of these (NO_WAIT is a macro used for 0 ticks, WAIT_FOREVER is its counterpart):

S_objLib_OBJ_TIMEOUT
    Timeout occured while pending on sempahore.

S_objLib_OBJ_UNAVAILABLE
    Would have blocked but NO_WAIT was specified.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> This almost seems too simple...

Unfortunately, yes. I think this is open to races involving re-use of thread IDs (and maybe use after free?). This potential is why the RTEMS version uses a barrier to prevent the thread from actually returning until it is joined (the rtems_id stays valid). Alternately, as with WIN32, allocated a separate epicsThreadId, with a ref. counter, that can outlive the OS task.

Revision history for this message
Andrew Johnson (anj) wrote :

The VxWorks implementation of osdThread is pretty thin — our thread ID is the OS's task ID, and in various places the code assumes the system is UP, so no memory barriers are required. I don't understand where else you could see races or user-after-frees that don't also exist in the native taskWait() API. Wind River implements taskWait() by adding the calling task to a wait queue that lives in the TCB of the task being waited for. The queue gets flushed (by a different task) to wake up all waiters when the task is deleted.

Now I agree that the current code is too simple since it doesn't check the status returned by taskWait() or look at errno, which obviously needs to be done and the subsequent behavior made to match the other implementations. I skipped doing that to have the discussion about returning an error status.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Maybe I'm not understanding. What is the lifetime of the handle returned by taskSpawn()? Will it ever be free()'d? Will it ever be reused? eg. Will a second call to taskSpawn() ever return the same ID?

My assumption is that IDs are not free()d, and can be reused (like a file descriptor). If this is the case, then it is possible for a race to occur between the time I get hold of an epicsThreadId, and the time I call epicsThreadMustJoin() on it. If in this interval the original thread exits, and suppose a new thread is started which happens to get the same ID. In this case I end up trying to join a different thread!

Resolving this is what necessitates joinable vs. detached as user configuration. Either the handle should remain valid, for the same thread, until joined. Or it should be freed immediately when the thread returns.

So the fact that the vxWorks epicsThreadCreateOpt() does nothing with epicsThreadOpts::joinable tells me that there is a bug.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Various changes, including the promised rename. I've also used epicsThreadMustJoin() in db_close_events(), for a considerable simplification.

review: Approve
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Was forgetting the vxworks issue

review: Needs Fixing
Revision history for this message
Andrew Johnson (anj) wrote :

AI on MAD to document semantics of epicsThreadJoin().
AI on AJ to finish VxWorks implementation.

Revision history for this message
Andrew Johnson (anj) wrote :

A working VxWorks 6.9+ implementation of epicsThreadMustJoin() is now included, and I added some more tests.

The changes made to asCaStop, dbCa and dbEvent mean the IOC now requires EPICS_THREAD_CAN_JOIN=1, so that macro is now obsolete and should be deleted. The minimum VxWorks version will also have to be bumped up to 6.9, which I documented in the Release Notes. The APS doesn't have a problem with that, although PSI might be unhappy.

On VxWorks a thread which sits waiting to be joined for longer than 60 seconds will log a warning message, as will a call to epicsThreadMustJoin(). The RTEMS and Windows implementations could probably do the same thing.

Could we replace the epicsThreadOptsDefaults() routine with a macro named EPICS_THREAD_OPTS_INIT which would be defined in osdThread.h (or even in epicsThread.h, see below). I'm looking to simplify this code:

    epicsThreadOpts opts;
    epicsThreadOptsDefaults(&opts);

into this:

    epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;

which removes the need for that epicsThreadOptsDefaults() routine. This name matches similar ones elsewhere in libCom (ELLLIST_INIT, EPICS_THREAD_ONCE_INIT etc.).

The only reason we need an OS-specific definition of those defaults is for the stack size parameter, which is an unsigned integer. Since we're making changes in this area, why don't we modify the epicsThreadCreateOpt() routines to check opts->stackSize and pass it through epicsThreadGetStackSize() if the value given matches any of the epicsThreadStackSizeClass values (none of which are legal stack sizes on any architecture)? Code could still pass in an arch-specific value, but this simplifies the API which I have always found very clunky.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Thanks Andrew.

> IOC now requires EPICS_THREAD_CAN_JOIN=1

Do you see tests failing with this change? If I was careful, then they shouldn't. The change should only result in transient resource leaks triggering false positives on a hypothetical target with EPICS_THREAD_CAN_JOIN=0 and valgrind support.

That said, I'm happy to be rid of EPICS_THREAD_CAN_JOIN.

> The RTEMS and Windows implementations could probably do the same thing.

I've no objection to this, but I'm not motivated to do this myself.

> ... EPICS_THREAD_OPTS_INIT ...

Also no objection.

review: Approve
Revision history for this message
Andrew Johnson (anj) wrote :

> The change should only result in transient resource leaks

Oops, sorry - you're right, I assumed that things would break if epicsThreadMustJoin() was a no-op, but looking more closely I guess they shouldn't, sorry! I have not looked in detail at the shutdown logic changes in dbEvent.c, I trust you to have tested that reasonably.

I have significantly adjusted and expanded the Release Notes again.

I have changed the EPICS_THREAD_CAN_JOIN macro, it is now only defined when a working epicsThreadMustJoin() is available, so external code can use a simple #ifdef to determine that the API is available and works on this Base version & target.

> ... EPICS_THREAD_OPTS_INIT ...

Done. External code can now use #ifdef EPICS_THREAD_OPTS_INIT to determine whether the epicsThreadCreateOpt() API is available on this Base version.

I just added another test to epicsThreadTest.cpp to check that a join actually delays the caller; the empty epicsThreadMustJoin() as compiled for VxWorks < 6.9 will return immediately. This check should detect that and fail on VxWorks 6.8 say, but is marked as ToDo when that's expected.

I'm happy with this now, others may wish to take a final look given my recent updates.

review: Approve
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> the shutdown logic changes in dbEvent.c

This is in part reverting d2b0e920012d4c9bc0eaecc0e092cedd776db578 now that a better solution is available.

> I trust you to have tested that reasonably.

Do I get some rope too?

anyway... Your changes look fine.

I would ask that future formatt cleanup be done in a separate commit. I note that the posix osdThread.c cleanup is going to cause conflicts if/when merging https://github.com/epics-base/epics-base/pull/24 If this was done with a separate commit, the merge process might be simplified by first reverting the cleanup.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
2index df78360..178afa4 100644
3--- a/documentation/RELEASE_NOTES.html
4+++ b/documentation/RELEASE_NOTES.html
5@@ -30,6 +30,7 @@ release.</p>
6
7 -->
8
9+<<<<<<< documentation/RELEASE_NOTES.html
10 <h1 align="center">EPICS Release 7.0.2.2</h1>
11
12 <h3>Build System changes</h3>
13@@ -127,6 +128,89 @@ than it used to be.</p>
14
15
16 <h1 align="center">EPICS Release 7.0.2</h1>
17+=======
18+<h3>VxWorks Minimum Version Recommendation</h3>
19+
20+<p>The implementation of the <code>epicsThreadMustJoin()</code> feature
21+described below uses facilities that were added to VxWorks in version 6.9. When
22+built against an older version of VxWorks the join functionality will not be
23+available and calls to <code>epicsThreadMustJoin()</code> will return
24+immediately. In this case the epicsThread.h header will not define the C macro
25+<code>EPICS_THREAD_CAN_JOIN</code> to allow alternate code to be provided for
26+these targets. The IOC's use of the join feature has been designed to work for
27+either situation.</p>
28+
29+
30+<h3>New and modified epicsThread APIs</h3>
31+
32+<h4><code>epicsThreadCreateOpt()</code></h4>
33+
34+<p>A new routine <code>epicsThreadCreateOpt()</code> is an alternative to
35+<code>epicsThreadCreate()</code> which takes some arguments via a structure
36+(<code>struct epicsThreadOpts</code>) to allow for future extensions.</p>
37+
38+<blockquote><pre>
39+typedef struct epicsThreadOpts {
40+ unsigned int priority;
41+ unsigned int stackSize;
42+ unsigned int joinable;
43+} epicsThreadOpts;
44+#define EPICS_THREAD_OPTS_INIT { \
45+ epicsThreadPriorityLow, epicsThreadStackMedium, 0}
46+
47+epicsThreadId epicsThreadCreateOpt(const char * name,
48+ EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts);
49+</pre></blockquote>
50+
51+<p>The final <code>opts</code> parameter may be <code>NULL</code> to use the
52+default values of thread priority (low) and stack size (medium). Callers wishing
53+to provide alternative settings for these thread options or to create a joinable
54+thread (see below) should create and pass in an <code>epicsThreadOpts</code>
55+structure as shown below. Always initialize one of these structures using the
56+<code>EPICS_THREAD_OPTS_INIT</code> macro to ensure that any additional fields
57+that get added in the future are set to their default values.</p>
58+
59+<blockquote><pre>
60+void startitup(void) {
61+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
62+ epicsThreadId tid;
63+
64+ opts.priority = epicsThreadPriorityMedium;
65+ tid = epicsThreadCreateOpt("my thread", &amp;threadMain, NULL, &amp;opts);
66+}
67+</pre></blockquote>
68+
69+<p>C or C++ Code that also needs to build on earlier versions of Base can use
70+<code>#ifdef&nbsp;EPICS_THREAD_OPTS_INIT</code> to determine whether the
71+<code>epicsThreadCreateOpt()</code> API is available on this Base version.</p>
72+
73+<h4>Thread stack sizes</h4>
74+
75+<p>The <code>stackSize</code> member of the <code>epicsThreadOpts</code>
76+structure and the equivalent parameters to the <code>epicsThreadCreate()</code>
77+and <code>epicsThreadMustCreate()</code> routines can now be passed either one
78+of the <code>epicsThreadStackSizeClass</code> enum values or a value returned
79+from the <code>epicsThreadGetStackSize()</code> routine.</p>
80+
81+<h4><code>epicsThreadMustJoin()</code></h4>
82+
83+<p>If the new <code>joinable</code> flag of an <code>epicsThreadOpts</code>
84+structure is non-zero (the default value is zero), the new API routine
85+<code>epicsThreadMustJoin()</code> <em>must</em> be called with the thread's
86+<code>epicsThreadId</code> when/after the thread exits, to free up thread
87+resources. This function will block until the thread's main function has
88+returned, allowing the parent to wait for its child thread. The child's
89+<code>epicsThreadId</code> will no longer be valid and should not be used after
90+the <code>epicsThreadMustJoin()</code> routine returns.</p>
91+
92+<p>A thread that was originally created with its joinable flag set may itself
93+call <code>epicsThreadMustJoin()</code>, passing in its own epicsThreadId. This
94+marks the thread as no longer being joinable, so it will then free the thread
95+resources itself when its main function returns. The <code>epicsThreadId</code>
96+of a thread that is not joinable gets invalidated as soon as its main function
97+returns.</p>
98+
99+>>>>>>> documentation/RELEASE_NOTES.html
100
101 <h3>Launchpad Bugs</h3>
102
103diff --git a/modules/database/src/ioc/as/asCa.c b/modules/database/src/ioc/as/asCa.c
104index d018044..21bb47f 100644
105--- a/modules/database/src/ioc/as/asCa.c
106+++ b/modules/database/src/ioc/as/asCa.c
107@@ -229,20 +229,23 @@ static void asCaTask(void)
108
109
110 void asCaStart(void)
111 {
112+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
113+
114+ opts.stackSize = epicsThreadGetStackSize(epicsThreadStackBig);
115+ opts.priority = epicsThreadPriorityScanLow - 3;
116+ opts.joinable = 1;
117+
118 if(asCaDebug) printf("asCaStart called\n");
119 if(firstTime) {
120- firstTime = FALSE;
121+ firstTime = FALSE;
122 asCaTaskLock=epicsMutexMustCreate();
123 asCaTaskWait=epicsEventMustCreate(epicsEventEmpty);
124 asCaTaskAddChannels=epicsEventMustCreate(epicsEventEmpty);
125 asCaTaskClearChannels=epicsEventMustCreate(epicsEventEmpty);
126- threadid = epicsThreadCreate("asCaTask",
127- (epicsThreadPriorityScanLow - 3),
128- epicsThreadGetStackSize(epicsThreadStackBig),
129- (EPICSTHREADFUNC)asCaTask,0);
130- if(threadid==0) {
131- errMessage(0,"asCaStart: taskSpawn Failure\n");
132- }
133+ threadid = epicsThreadCreateOpt("asCaTask", (EPICSTHREADFUNC)asCaTask, 0, &opts);
134+ if(threadid==0) {
135+ errMessage(0,"asCaStart: taskSpawn Failure\n");
136+ }
137 }
138 epicsMutexMustLock(asCaTaskLock);
139 epicsEventSignal(asCaTaskAddChannels);
140@@ -260,6 +263,8 @@ void asCaStop(void)
141 epicsEventMustWait(asCaTaskWait);
142 if(asCaDebug) printf("asCaStop done\n");
143 epicsMutexUnlock(asCaTaskLock);
144+ epicsThreadMustJoin(threadid);
145+ threadid = 0;
146 }
147
148 int ascar(int level) { return ascarFP(stdout,level);}
149diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c
150index 843fbfc..45e178c 100644
151--- a/modules/database/src/ioc/db/dbCa.c
152+++ b/modules/database/src/ioc/db/dbCa.c
153@@ -68,6 +68,7 @@ static volatile enum dbCaCtl_t {
154 ctlInit, ctlRun, ctlPause, ctlExit
155 } dbCaCtl;
156 static epicsEventId startStopEvent;
157+static epicsThreadId dbCaWorker;
158
159 struct ca_client_context * dbCaClientContext;
160
161@@ -258,10 +259,18 @@ void dbCaShutdown(void)
162 dbCaCtl = ctlExit;
163 epicsEventSignal(workListEvent);
164 epicsEventMustWait(startStopEvent);
165+ if(dbCaWorker)
166+ epicsThreadMustJoin(dbCaWorker);
167 }
168
169 static void dbCaLinkInitImpl(int isolate)
170 {
171+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
172+
173+ opts.stackSize = epicsThreadGetStackSize(epicsThreadStackBig);
174+ opts.priority = epicsThreadPriorityMedium;
175+ opts.joinable = 1;
176+
177 dbServiceIsolate = isolate;
178 dbServiceIOInit();
179
180@@ -274,9 +283,8 @@ static void dbCaLinkInitImpl(int isolate)
181 startStopEvent = epicsEventMustCreate(epicsEventEmpty);
182 dbCaCtl = ctlPause;
183
184- epicsThreadCreate("dbCaLink", epicsThreadPriorityMedium,
185- epicsThreadGetStackSize(epicsThreadStackBig),
186- dbCaTask, NULL);
187+ dbCaWorker = epicsThreadCreateOpt("dbCaLink", dbCaTask, NULL, &opts);
188+ /* wait for worker to startup and initialize dbCaClientContext */
189 epicsEventMustWait(startStopEvent);
190 }
191
192diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c
193index 77b12de..5e0f4db 100644
194--- a/modules/database/src/ioc/db/dbEvent.c
195+++ b/modules/database/src/ioc/db/dbEvent.c
196@@ -84,7 +84,6 @@ struct event_user {
197 epicsMutexId lock;
198 epicsEventId ppendsem; /* Wait while empty */
199 epicsEventId pflush_sem; /* wait for flush */
200- epicsEventId pexitsem; /* wait for event task to join */
201
202 EXTRALABORFUNC *extralabor_sub;/* off load to event task */
203 void *extralabor_arg;/* parameter to above */
204@@ -123,8 +122,6 @@ static char *EVENT_PEND_NAME = "eventTask";
205
206 static struct evSubscrip canceledEvent;
207
208-static epicsMutexId stopSync;
209-
210 static unsigned short ringSpace ( const struct event_que *pevq )
211 {
212 if ( pevq->evque[pevq->putix] == EVENTQEMPTY ) {
213@@ -266,10 +263,6 @@ dbEventCtx db_init_events (void)
214 {
215 struct event_user * evUser;
216
217- if (!stopSync) {
218- stopSync = epicsMutexMustCreate();
219- }
220-
221 if (!dbevEventUserFreeList) {
222 freeListInitPvt(&dbevEventUserFreeList,
223 sizeof(struct event_user),8);
224@@ -293,9 +286,6 @@ dbEventCtx db_init_events (void)
225 return NULL;
226 }
227
228- /* Flag will be cleared when event task starts */
229- evUser->pendexit = TRUE;
230-
231 evUser->firstque.evUser = evUser;
232 evUser->firstque.writelock = epicsMutexCreate();
233 if (!evUser->firstque.writelock)
234@@ -310,9 +300,6 @@ dbEventCtx db_init_events (void)
235 evUser->lock = epicsMutexCreate();
236 if (!evUser->lock)
237 goto fail;
238- evUser->pexitsem = epicsEventCreate(epicsEventEmpty);
239- if (!evUser->pexitsem)
240- goto fail;
241
242 evUser->flowCtrlMode = FALSE;
243 evUser->extraLaborBusy = FALSE;
244@@ -327,8 +314,6 @@ fail:
245 epicsEventDestroy (evUser->ppendsem);
246 if(evUser->pflush_sem)
247 epicsEventDestroy (evUser->pflush_sem);
248- if(evUser->pexitsem)
249- epicsEventDestroy (evUser->pexitsem);
250 freeListFree(dbevEventUserFreeList,evUser);
251 return NULL;
252 }
253@@ -349,7 +334,6 @@ epicsShareFunc void db_cleanup_events(void)
254 dbevFieldLogFreeList = NULL;
255 }
256
257- /* intentionally leak stopSync to avoid possible shutdown races */
258 /*
259 * DB_CLOSE_EVENTS()
260 *
261@@ -371,30 +355,15 @@ void db_close_events (dbEventCtx ctx)
262 * hazardous to the system's health.
263 */
264 epicsMutexMustLock ( evUser->lock );
265- if(!evUser->pendexit) { /* event task running */
266- evUser->pendexit = TRUE;
267- epicsMutexUnlock ( evUser->lock );
268-
269- /* notify the waiting task */
270- epicsEventSignal(evUser->ppendsem);
271- /* wait for task to exit */
272- epicsEventMustWait(evUser->pexitsem);
273-
274- epicsMutexMustLock ( evUser->lock );
275- }
276-
277+ evUser->pendexit = TRUE;
278 epicsMutexUnlock ( evUser->lock );
279
280- epicsMutexMustLock (stopSync);
281-
282- epicsEventDestroy(evUser->pexitsem);
283- epicsEventDestroy(evUser->ppendsem);
284- epicsEventDestroy(evUser->pflush_sem);
285- epicsMutexDestroy(evUser->lock);
286-
287- epicsMutexUnlock (stopSync);
288+ /* notify the waiting task */
289+ epicsEventSignal(evUser->ppendsem);
290
291- freeListFree(dbevEventUserFreeList, evUser);
292+ if(evUser->taskid)
293+ epicsThreadMustJoin(evUser->taskid);
294+ /* evUser has been deleted by the worker */
295 }
296
297 /*
298@@ -1074,16 +1043,13 @@ static void event_task (void *pParm)
299 }
300 }
301
302- taskwdRemove(epicsThreadGetIdSelf());
303-
304- /* use stopSync to ensure pexitsem is not destroy'd
305- * until epicsEventSignal() has returned.
306- */
307- epicsMutexMustLock (stopSync);
308+ epicsEventDestroy(evUser->ppendsem);
309+ epicsEventDestroy(evUser->pflush_sem);
310+ epicsMutexDestroy(evUser->lock);
311
312- epicsEventSignal(evUser->pexitsem);
313+ freeListFree(dbevEventUserFreeList, evUser);
314
315- epicsMutexUnlock(stopSync);
316+ taskwdRemove(epicsThreadGetIdSelf());
317
318 return;
319 }
320@@ -1096,6 +1062,11 @@ int db_start_events (
321 void *init_func_arg, unsigned osiPriority )
322 {
323 struct event_user * const evUser = (struct event_user *) ctx;
324+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
325+
326+ opts.stackSize = epicsThreadGetStackSize(epicsThreadStackMedium);
327+ opts.priority = osiPriority;
328+ opts.joinable = 1;
329
330 epicsMutexMustLock ( evUser->lock );
331
332@@ -1113,15 +1084,12 @@ int db_start_events (
333 if (!taskname) {
334 taskname = EVENT_PEND_NAME;
335 }
336- evUser->taskid = epicsThreadCreate (
337- taskname, osiPriority,
338- epicsThreadGetStackSize(epicsThreadStackMedium),
339- event_task, (void *)evUser);
340+ evUser->taskid = epicsThreadCreateOpt (
341+ taskname, event_task, (void *)evUser, &opts);
342 if (!evUser->taskid) {
343 epicsMutexUnlock ( evUser->lock );
344 return DB_EVENT_ERROR;
345 }
346- evUser->pendexit = FALSE;
347 epicsMutexUnlock ( evUser->lock );
348 return DB_EVENT_OK;
349 }
350diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp
351index 8a788be..8255fdc 100644
352--- a/modules/database/test/ioc/db/dbChArrTest.cpp
353+++ b/modules/database/test/ioc/db/dbChArrTest.cpp
354@@ -36,7 +36,7 @@
355 #include "iocInit.h"
356 #include "iocsh.h"
357 #include "dbChannel.h"
358-#include "epicsUnitTest.h"
359+#include "dbUnitTest.h"
360 #include "testMain.h"
361 #include "osiFileName.h"
362
363@@ -197,50 +197,33 @@ static void check(short dbr_type) {
364 dbChannelDelete(pch);
365 }
366
367-static dbEventCtx evtctx;
368-
369-extern "C" {
370-static void dbChArrTestCleanup(void* junk)
371-{
372- dbFreeBase(pdbbase);
373- registryFree();
374- pdbbase=0;
375-
376- db_close_events(evtctx);
377-
378- dbmfFreeChunks();
379-}
380-}
381-
382 MAIN(dbChArrTest)
383 {
384 testPlan(102);
385
386 /* Prepare the IOC */
387+ testdbPrepare();
388
389 epicsEnvSet("EPICS_CA_SERVER_PORT", server_port);
390
391- if (dbReadDatabase(&pdbbase, "dbChArrTest.dbd",
392- "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
393- "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
394- testAbort("Database description not loaded");
395+ testdbReadDatabase("dbChArrTest.dbd",
396+ "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
397+ "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL);
398
399 dbChArrTest_registerRecordDeviceDriver(pdbbase);
400
401- if (dbReadDatabase(&pdbbase, "dbChArrTest.db",
402- "." OSI_PATH_LIST_SEPARATOR "..", NULL))
403- testAbort("Test database not loaded");
404-
405- epicsAtExit(&dbChArrTestCleanup,NULL);
406+ testdbReadDatabase("dbChArrTest.db",
407+ "." OSI_PATH_LIST_SEPARATOR "..", NULL);
408
409- /* Start the IOC */
410-
411- iocInit();
412- evtctx = db_init_events();
413+ testIocInitOk();
414
415 check(DBR_LONG);
416 check(DBR_DOUBLE);
417 check(DBR_STRING);
418
419+ testIocShutdownOk();
420+
421+ testdbCleanup();
422+
423 return testDone();
424 }
425diff --git a/modules/libcom/RTEMS/rtems_config.c b/modules/libcom/RTEMS/rtems_config.c
426index 147c08b..796b104 100644
427--- a/modules/libcom/RTEMS/rtems_config.c
428+++ b/modules/libcom/RTEMS/rtems_config.c
429@@ -27,6 +27,7 @@
430 #endif
431
432 #define CONFIGURE_MAXIMUM_TASKS rtems_resource_unlimited(30)
433+#define CONFIGURE_MAXIMUM_BARRIERS rtems_resource_unlimited(30)
434 #define CONFIGURE_MAXIMUM_SEMAPHORES rtems_resource_unlimited(500)
435 #define CONFIGURE_MAXIMUM_TIMERS rtems_resource_unlimited(20)
436 #define CONFIGURE_MAXIMUM_MESSAGE_QUEUES rtems_resource_unlimited(5)
437diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp
438index 892d73d..92f8338 100644
439--- a/modules/libcom/src/osi/epicsThread.cpp
440+++ b/modules/libcom/src/osi/epicsThread.cpp
441@@ -31,6 +31,18 @@
442
443 using namespace std;
444
445+epicsThreadId epicsShareAPI epicsThreadCreate (
446+ const char * name, unsigned int priority, unsigned int stackSize,
447+ EPICSTHREADFUNC funptr,void * parm )
448+{
449+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
450+ opts.priority = priority;
451+ opts.stackSize = stackSize;
452+ opts.joinable = 0;
453+
454+ return epicsThreadCreateOpt(name, funptr, parm, &opts);
455+}
456+
457 epicsThreadRunable::~epicsThreadRunable () {}
458 void epicsThreadRunable::run () {}
459 void epicsThreadRunable::show ( unsigned int ) const {}
460@@ -141,6 +153,13 @@ bool epicsThread::exitWait ( const double delay ) throw ()
461 if ( this->pThreadDestroyed ) {
462 *this->pThreadDestroyed = true;
463 }
464+ if(!joined) {
465+ {
466+ epicsGuard < epicsMutex > guard ( this->mutex );
467+ joined = true;
468+ }
469+ epicsThreadMustJoin(this->id);
470+ }
471 return true;
472 }
473 epicsTime exitWaitBegin = epicsTime::getCurrent ();
474@@ -154,6 +173,12 @@ bool epicsThread::exitWait ( const double delay ) throw ()
475 epicsTime current = epicsTime::getCurrent ();
476 exitWaitElapsed = current - exitWaitBegin;
477 }
478+ if(this->terminated && !joined) {
479+ joined = true;
480+
481+ epicsGuardRelease < epicsMutex > unguard ( guard );
482+ epicsThreadMustJoin(this->id);
483+ }
484 }
485 catch ( std :: exception & except ) {
486 errlogPrintf (
487@@ -177,11 +202,18 @@ epicsThread::epicsThread (
488 epicsThreadRunable & runableIn, const char * pName,
489 unsigned stackSize, unsigned priority ) :
490 runable ( runableIn ), id ( 0 ), pThreadDestroyed ( 0 ),
491- begin ( false ), cancel ( false ), terminated ( false )
492+ begin ( false ), cancel ( false ), terminated ( false ),
493+ joined ( false )
494 {
495- this->id = epicsThreadCreate (
496- pName, priority, stackSize, epicsThreadCallEntryPoint,
497- static_cast < void * > ( this ) );
498+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
499+ opts.stackSize = stackSize;
500+ opts.priority = priority;
501+ opts.joinable = 1;
502+
503+ this->id = epicsThreadCreateOpt(
504+ pName, epicsThreadCallEntryPoint,
505+ static_cast < void * > ( this ),
506+ &opts);
507 if ( ! this->id ) {
508 throw unableToCreateThread ();
509 }
510diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h
511index 84b2c47..da16a0b 100644
512--- a/modules/libcom/src/osi/epicsThread.h
513+++ b/modules/libcom/src/osi/epicsThread.h
514@@ -45,6 +45,7 @@ typedef enum {
515 epicsThreadBooleanStatusFail, epicsThreadBooleanStatusSuccess
516 } epicsThreadBooleanStatus;
517
518+/** Lookup target specific default stack size */
519 epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize(
520 epicsThreadStackSizeClass size);
521
522@@ -54,6 +55,20 @@ typedef struct epicsThreadOSD *epicsThreadId;
523 typedef epicsThreadId epicsThreadOnceId;
524 #define EPICS_THREAD_ONCE_INIT 0
525
526+/** Perform one-time initialization.
527+ *
528+ * Run the provided function if it has not run, and is not running.
529+ *
530+ * @post The provided function has been run.
531+ *
532+ * @code
533+ * static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
534+ * static void myInitFunc(void *arg) { ... }
535+ * static void some Function(void) {
536+ * epicsThreadOnce(&onceId, &myInitFunc, NULL);
537+ * }
538+ * @endcode
539+ */
540 epicsShareFunc void epicsShareAPI epicsThreadOnce(
541 epicsThreadOnceId *id, EPICSTHREADFUNC, void *arg);
542
543@@ -65,46 +80,140 @@ epicsShareFunc void epicsThreadRealtimeLock(void);
544
545 epicsShareFunc void epicsShareAPI epicsThreadExitMain(void);
546
547+/** For use with epicsThreadCreateOpt() */
548+typedef struct epicsThreadOpts {
549+ /** Thread priority in OSI range (cf. epicsThreadPriority*) */
550+ unsigned int priority;
551+ /** Thread stack size, either in bytes for this architecture or
552+ * an enum epicsThreadStackSizeClass value.
553+ */
554+ unsigned int stackSize;
555+ /** Should thread be joinable? (default (0) is not joinable).
556+ * If joinable=1, then epicsThreadMustJoin() must be called for cleanup thread resources.
557+ */
558+ unsigned int joinable;
559+} epicsThreadOpts;
560+
561+/** Default initial values for epicsThreadOpts
562+ * Applications should always use this macro to initialize an epicsThreadOpts
563+ * structure. Additional fields may be added in the future, and the order of
564+ * the fields might also change, thus code that assumes the above definition
565+ * might break if these rules are not followed.
566+ */
567+#define EPICS_THREAD_OPTS_INIT { \
568+ epicsThreadPriorityLow, epicsThreadStackMedium, 0}
569+
570+/** @brief Allocate and start a new OS thread.
571+ * @param name A name describing this thread. Appears in various log and error message.
572+ * @param funptr The thread main function.
573+ * @param parm Passed to thread main function.
574+ * @param opts Modifiers for the new thread, or NULL to use target specific defaults.
575+ * @return NULL on error
576+ */
577+epicsShareFunc epicsThreadId epicsThreadCreateOpt (
578+ const char * name,
579+ EPICSTHREADFUNC funptr, void * parm,
580+ const epicsThreadOpts *opts );
581+/** Short-hand for epicsThreadCreateOpt() to create an un-joinable thread. */
582 epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (
583 const char * name, unsigned int priority, unsigned int stackSize,
584 EPICSTHREADFUNC funptr,void * parm );
585+/** Short-hand for epicsThreadCreateOpt() to create an un-joinable thread.
586+ * On error calls cantProceed()
587+ */
588 epicsShareFunc epicsThreadId epicsShareAPI epicsThreadMustCreate (
589 const char * name, unsigned int priority, unsigned int stackSize,
590- EPICSTHREADFUNC funptr,void * parm );
591+ EPICSTHREADFUNC funptr,void * parm );
592+
593+/* This gets undefined in osdThread.h on VxWorks < 6.9 */
594+#define EPICS_THREAD_CAN_JOIN
595+/** Wait for a joinable thread to exit (return from its main function) */
596+epicsShareFunc void epicsThreadMustJoin(epicsThreadId id);
597+/** Block the current thread until epicsThreadResume(). */
598 epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void);
599+/** Resume a thread suspended with epicsThreadSuspendSelf() */
600 epicsShareFunc void epicsShareAPI epicsThreadResume(epicsThreadId id);
601+/** Return thread OSI priority */
602 epicsShareFunc unsigned int epicsShareAPI epicsThreadGetPriority(
603 epicsThreadId id);
604+/** Return thread OSI priority */
605 epicsShareFunc unsigned int epicsShareAPI epicsThreadGetPrioritySelf(void);
606+/** Change OSI priority of target thread. */
607 epicsShareFunc void epicsShareAPI epicsThreadSetPriority(
608 epicsThreadId id,unsigned int priority);
609+/** Lookup the next usage OSI priority such that priority > *pPriorityJustBelow
610+ * if this is possible with the current target configuration and privlages.
611+ */
612 epicsShareFunc epicsThreadBooleanStatus epicsShareAPI
613 epicsThreadHighestPriorityLevelBelow (
614 unsigned int priority, unsigned *pPriorityJustBelow);
615+/** Lookup the next usage OSI priority such that priority < *pPriorityJustBelow
616+ * if this is possible with the current target configuration and privlages.
617+ */
618 epicsShareFunc epicsThreadBooleanStatus epicsShareAPI
619 epicsThreadLowestPriorityLevelAbove (
620 unsigned int priority, unsigned *pPriorityJustAbove);
621+/** Test if two thread IDs actually refer to the same OS thread */
622 epicsShareFunc int epicsShareAPI epicsThreadIsEqual(
623 epicsThreadId id1, epicsThreadId id2);
624+/** Test if thread has been suspended with epicsThreadSuspendSelf() */
625 epicsShareFunc int epicsShareAPI epicsThreadIsSuspended(epicsThreadId id);
626+/** @brief Block the calling thread for at least the specified time.
627+ * @param seconds Time to wait in seconds. Values <=0 blocks for the shortest possible time.
628+ */
629 epicsShareFunc void epicsShareAPI epicsThreadSleep(double seconds);
630+/** @brief Query a value approximating the OS timer/scheduler resolution.
631+ * @return A value in seconds >=0
632+ *
633+ * @warning On targets other than vxWorks and RTEMS, the quantum value often isn't
634+ * meaningful. Use of this function is discouraged in portable code.
635+ */
636 epicsShareFunc double epicsShareAPI epicsThreadSleepQuantum(void);
637+/** Find an epicsThreadId associated with the current thread.
638+ * For non-EPICS threads, a new epicsThreadId may be allocated.
639+ */
640 epicsShareFunc epicsThreadId epicsShareAPI epicsThreadGetIdSelf(void);
641+/** Attempt to find the first instance of a thread by name.
642+ * @return An epicsThreadId, or NULL if no such thread is currently running.
643+ * Note that a non-NULL ID may still be invalid if this call races
644+ * with thread exit.
645+ *
646+ * @warning Safe use of this function requires external knowledge that this
647+ * thread will not return.
648+ */
649 epicsShareFunc epicsThreadId epicsShareAPI epicsThreadGetId(const char *name);
650+/** Return a value approximating the number of threads which this target
651+ * can run in parallel. This value is advisory.
652+ * @return >=1
653+ */
654 epicsShareFunc int epicsThreadGetCPUs(void);
655
656+/** Return the name of the current thread.
657+ *
658+ * @return Never NULL. Storage lifetime tied to epicsThreadId.
659+ *
660+ * This is either a copy of the string passed to epicsThread*Create*(),
661+ * or an arbitrary unique string for non-EPICS threads.
662+ */
663 epicsShareFunc const char * epicsShareAPI epicsThreadGetNameSelf(void);
664
665-/* For epicsThreadGetName name is guaranteed to be null terminated */
666-/* size is size of buffer to hold name (including terminator) */
667-/* Failure results in an empty string stored in name */
668+/** Copy out the thread name into the provided buffer.
669+ *
670+ * Guaranteed to be null terminated.
671+ * size is number of bytes in buffer to hold name (including terminator).
672+ * Failure results in an empty string stored in name.
673+ */
674 epicsShareFunc void epicsShareAPI epicsThreadGetName(
675 epicsThreadId id, char *name, size_t size);
676
677 epicsShareFunc int epicsShareAPI epicsThreadIsOkToBlock(void);
678 epicsShareFunc void epicsShareAPI epicsThreadSetOkToBlock(int isOkToBlock);
679
680+/** Print to stdout information about all running EPICS threads.
681+ * @param level 0 prints minimal output. Higher values print more details.
682+ */
683 epicsShareFunc void epicsShareAPI epicsThreadShowAll(unsigned int level);
684+/** Print info about a single EPICS thread. */
685 epicsShareFunc void epicsShareAPI epicsThreadShow(
686 epicsThreadId id,unsigned int level);
687
688@@ -115,10 +224,17 @@ epicsShareFunc int epicsThreadHookDelete(EPICS_THREAD_HOOK_ROUTINE hook);
689 epicsShareFunc void epicsThreadHooksShow(void);
690 epicsShareFunc void epicsThreadMap(EPICS_THREAD_HOOK_ROUTINE func);
691
692+/** Thread local storage */
693 typedef struct epicsThreadPrivateOSD * epicsThreadPrivateId;
694+/** Allocate a new thread local variable.
695+ * This variable will initially hold NULL for each thread.
696+ */
697 epicsShareFunc epicsThreadPrivateId epicsShareAPI epicsThreadPrivateCreate(void);
698+/** Free a thread local variable */
699 epicsShareFunc void epicsShareAPI epicsThreadPrivateDelete(epicsThreadPrivateId id);
700+/** Update thread local variable */
701 epicsShareFunc void epicsShareAPI epicsThreadPrivateSet(epicsThreadPrivateId,void *);
702+/** Fetch the current value of a thread local variable */
703 epicsShareFunc void * epicsShareAPI epicsThreadPrivateGet(epicsThreadPrivateId);
704
705 #ifdef __cplusplus
706@@ -130,33 +246,64 @@ epicsShareFunc void * epicsShareAPI epicsThreadPrivateGet(epicsThreadPrivateId);
707 #include "epicsEvent.h"
708 #include "epicsMutex.h"
709
710+//! Interface used with class epicsThread
711 class epicsShareClass epicsThreadRunable {
712 public:
713 virtual ~epicsThreadRunable () = 0;
714+ //! Thread main function.
715+ //! C++ exceptions which propagate from this method will be caught and a warning printed.
716+ //! No other action is taken.
717 virtual void run () = 0;
718+ //! Optional. Called via epicsThread::show()
719 virtual void show ( unsigned int level ) const;
720 };
721
722 extern "C" void epicsThreadCallEntryPoint ( void * );
723
724+/** @brief An OS thread
725+ *
726+ * A wrapper around the epicsThread* C API.
727+ *
728+ * @note Threads must be start() ed.
729+ */
730 class epicsShareClass epicsThread {
731 public:
732+ /** Create a new thread with the provided information.
733+ *
734+ * cf. epicsThreadOpts
735+ * @note Threads must be start() ed.
736+ * @throws epicsThread::unableToCreateThread on error.
737+ */
738 epicsThread ( epicsThreadRunable &,const char *name, unsigned int stackSize,
739 unsigned int priority=epicsThreadPriorityLow );
740 ~epicsThread () throw ();
741+ //! Actually start the thread.
742 void start () throw ();
743+ //! Wait for the thread epicsRunnable::run() to return.
744 void exitWait () throw ();
745+ //! Wait for the thread epicsRunnable::run() to return.
746+ //! @param delay Wait up to this many seconds.
747+ //! @returns true if run() returned. false on timeout.
748 bool exitWait ( const double delay ) throw ();
749+ //! @throws A special exitException which will be caught and ignored.
750+ //! @note This exitException doesn't not derive from std::exception
751 static void exit ();
752+ //! cf. epicsThreadResume()
753 void resume () throw ();
754+ //! cf. epicsThreadGetName();
755 void getName ( char * name, size_t size ) const throw ();
756+ //! cf. epicsThreadGetIdSelf()()
757 epicsThreadId getId () const throw ();
758+ //! cf. epicsThreadGetPriority()
759 unsigned int getPriority () const throw ();
760+ //! cf. epicsThreadSetPriority()
761 void setPriority ( unsigned int ) throw ();
762 bool priorityIsEqual ( const epicsThread & ) const throw ();
763 bool isSuspended () const throw ();
764+ //! @return true if call through this thread's epicsRunnable::run()
765 bool isCurrentThread () const throw ();
766 bool operator == ( const epicsThread & ) const throw ();
767+ //! Say something interesting about this thread to stdout.
768 void show ( unsigned level ) const throw ();
769
770 /* these operate on the current thread */
771@@ -178,6 +325,7 @@ private:
772 bool begin;
773 bool cancel;
774 bool terminated;
775+ bool joined;
776
777 bool beginWait () throw ();
778 epicsThread ( const epicsThread & );
779diff --git a/modules/libcom/src/osi/os/Linux/osdThread.h b/modules/libcom/src/osi/os/Linux/osdThread.h
780index 7d2a486..bb1fdcb 100644
781--- a/modules/libcom/src/osi/os/Linux/osdThread.h
782+++ b/modules/libcom/src/osi/os/Linux/osdThread.h
783@@ -22,6 +22,7 @@ extern "C" {
784
785 typedef struct epicsThreadOSD {
786 ELLNODE node;
787+ int refcnt;
788 pthread_t tid;
789 pid_t lwpId;
790 pthread_attr_t attr;
791@@ -35,6 +36,7 @@ typedef struct epicsThreadOSD {
792 int isRealTimeScheduled;
793 int isOnThreadList;
794 unsigned int osiPriority;
795+ int joinable;
796 char name[1]; /* actually larger */
797 } epicsThreadOSD;
798
799diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c
800index 769e958..bdcd8c1 100644
801--- a/modules/libcom/src/osi/os/RTEMS/osdThread.c
802+++ b/modules/libcom/src/osi/os/RTEMS/osdThread.c
803@@ -35,6 +35,7 @@
804 #include "osiUnistd.h"
805 #include "osdInterrupt.h"
806 #include "epicsExit.h"
807+#include "epicsAtomic.h"
808
809 epicsShareFunc void osdThreadHooksRun(epicsThreadId id);
810 epicsShareFunc void osdThreadHooksRunMain(epicsThreadId id);
811@@ -47,6 +48,9 @@ struct taskVar {
812 struct taskVar *back;
813 char *name;
814 rtems_id id;
815+ rtems_id join_barrier; /* only valid if joinable */
816+ int refcnt;
817+ int joinable;
818 EPICSTHREADFUNC funptr;
819 void *parm;
820 unsigned int threadVariableCapacity;
821@@ -163,6 +167,22 @@ taskVarUnlock (void)
822 epicsMutexOsdUnlock (taskVarMutex);
823 }
824
825+static
826+void taskUnref(struct taskVar *v)
827+{
828+ int ref = epicsAtomicDecrIntT(&v->refcnt);
829+ assert(ref>=0);
830+ if(ref>0) return;
831+
832+
833+ if (v->joinable) {
834+ rtems_barrier_delete(v->join_barrier);
835+ }
836+ free (v->threadVariables);
837+ free (v->name);
838+ free (v);
839+}
840+
841 /*
842 * EPICS threads destroy themselves by returning from the thread entry function.
843 * This simple wrapper provides the same semantics on RTEMS.
844@@ -183,9 +203,12 @@ threadWrapper (rtems_task_argument arg)
845 if (v->forw)
846 v->forw->back = v->back;
847 taskVarUnlock ();
848- free (v->threadVariables);
849- free (v->name);
850- free (v);
851+ if(v->joinable) {
852+ rtems_status_code sc = rtems_barrier_wait(v->join_barrier, RTEMS_NO_TIMEOUT);
853+ if(sc!=RTEMS_SUCCESSFUL)
854+ cantProceed("oops %s\n", rtems_status_text(sc));
855+ }
856+ taskUnref(v);
857 rtems_task_delete (RTEMS_SELF);
858 }
859
860@@ -196,21 +219,34 @@ void epicsThreadExitMain (void)
861 {
862 }
863
864-static void
865+static rtems_status_code
866 setThreadInfo(rtems_id tid, const char *name, EPICSTHREADFUNC funptr,
867- void *parm)
868+ void *parm, int joinable)
869 {
870 struct taskVar *v;
871 uint32_t note;
872- rtems_status_code sc;
873+ rtems_status_code sc = RTEMS_SUCCESSFUL;
874
875 v = mallocMustSucceed (sizeof *v, "epicsThreadCreate_vars");
876 v->name = epicsStrDup(name);
877 v->id = tid;
878 v->funptr = funptr;
879 v->parm = parm;
880+ v->joinable = joinable;
881+ v->refcnt = joinable ? 2 : 1;
882 v->threadVariableCapacity = 0;
883 v->threadVariables = NULL;
884+ if (joinable) {
885+ char c[3];
886+ strncpy(c, v->name, 3);
887+ sc = rtems_barrier_create(rtems_build_name('~', c[0], c[1], c[2]),
888+ RTEMS_BARRIER_AUTOMATIC_RELEASE | RTEMS_LOCAL,
889+ 2, &v->join_barrier);
890+ if (sc != RTEMS_SUCCESSFUL) {
891+ free(v);
892+ return sc;
893+ }
894+ }
895 note = (uint32_t)v;
896 rtems_task_set_note (tid, RTEMS_NOTEPAD_TASKVAR, note);
897 taskVarLock ();
898@@ -222,10 +258,14 @@ setThreadInfo(rtems_id tid, const char *name, EPICSTHREADFUNC funptr,
899 taskVarUnlock ();
900 if (funptr) {
901 sc = rtems_task_start (tid, threadWrapper, (rtems_task_argument)v);
902- if (sc != RTEMS_SUCCESSFUL)
903- errlogPrintf ("setThreadInfo: Can't start %s: %s\n",
904- name, rtems_status_text(sc));
905 }
906+ if (sc != RTEMS_SUCCESSFUL) {
907+ if (joinable) {
908+ rtems_barrier_delete(v->join_barrier);
909+ }
910+ free(v);
911+ }
912+ return sc;
913 }
914
915 /*
916@@ -247,7 +287,8 @@ epicsThreadInit (void)
917 if (!onceMutex || !taskVarMutex)
918 cantProceed("epicsThreadInit() can't create global mutexes\n");
919 rtems_task_ident (RTEMS_SELF, 0, &tid);
920- setThreadInfo (tid, "_main_", NULL, NULL);
921+ if(setThreadInfo (tid, "_main_", NULL, NULL, 0) != RTEMS_SUCCESSFUL)
922+ cantProceed("epicsThreadInit() unable to setup _main_");
923 osdThreadHooksRunMain((epicsThreadId)tid);
924 initialized = 1;
925 epicsThreadCreate ("ImsgDaemon", 99,
926@@ -263,15 +304,26 @@ void epicsThreadRealtimeLock(void)
927 * Create and start a new thread
928 */
929 epicsThreadId
930-epicsThreadCreate (const char *name,
931- unsigned int priority, unsigned int stackSize,
932- EPICSTHREADFUNC funptr,void *parm)
933+epicsThreadCreateOpt (
934+ const char * name,
935+ EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts )
936 {
937+ unsigned int stackSize;
938 rtems_id tid;
939 rtems_status_code sc;
940 char c[4];
941
942- if (!initialized) epicsThreadInit();
943+ if (!initialized)
944+ epicsThreadInit();
945+
946+ if (!opts) {
947+ static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT;
948+ opts = &opts_default;
949+ }
950+ stackSize = opts->stackSize;
951+ if (stackSize <= epicsThreadStackBig)
952+ stackSize = epicsThreadGetStackSize(stackSize);
953+
954 if (stackSize < RTEMS_MINIMUM_STACK_SIZE) {
955 errlogPrintf ("Warning: epicsThreadCreate %s illegal stackSize %d\n",
956 name, stackSize);
957@@ -279,7 +331,7 @@ epicsThreadCreate (const char *name,
958 }
959 strncpy (c, name, sizeof c);
960 sc = rtems_task_create (rtems_build_name (c[0], c[1], c[2], c[3]),
961- epicsThreadGetOssPriorityValue (priority),
962+ epicsThreadGetOssPriorityValue (opts->priority),
963 stackSize,
964 RTEMS_PREEMPT|RTEMS_NO_TIMESLICE|RTEMS_NO_ASR|RTEMS_INTERRUPT_LEVEL(0),
965 RTEMS_FLOATING_POINT|RTEMS_LOCAL,
966@@ -289,7 +341,13 @@ epicsThreadCreate (const char *name,
967 name, rtems_status_text(sc));
968 return 0;
969 }
970- setThreadInfo (tid, name, funptr,parm);
971+ sc = setThreadInfo (tid, name, funptr, parm, opts->joinable);
972+ if (sc != RTEMS_SUCCESSFUL) {
973+ errlogPrintf ("epicsThreadCreate create failure during setup for %s: %s\n",
974+ name, rtems_status_text(sc));
975+ rtems_task_delete(tid);
976+ return 0;
977+ }
978 return (epicsThreadId)tid;
979 }
980
981@@ -305,6 +363,51 @@ threadMustCreate (const char *name,
982 return tid;
983 }
984
985+void epicsThreadMustJoin(epicsThreadId id)
986+{
987+ rtems_id target_tid = (rtems_id)id, self_tid;
988+ struct taskVar *v = 0;
989+
990+ rtems_task_ident (RTEMS_SELF, 0, &self_tid);
991+
992+ {
993+ uint32_t note;
994+ rtems_task_get_note (target_tid, RTEMS_NOTEPAD_TASKVAR, &note);
995+ v = (void *)note;
996+ }
997+ /* 'v' may be NULL if 'id' represents a non-EPICS thread other than _main_. */
998+
999+ if(!v || !v->joinable) {
1000+ if(epicsThreadGetIdSelf()==id) {
1001+ errlogPrintf("Warning: %s thread self-join of unjoinable\n", v ? v->name : "non-EPICS thread");
1002+
1003+ } else {
1004+ /* try to error nicely, however in all likelyhood de-ref of
1005+ * 'id' has already caused SIGSEGV as we are racing thread exit,
1006+ * which free's 'id'.
1007+ */
1008+ cantProceed("Error: %s thread not joinable.\n", v->name);
1009+ }
1010+ return;
1011+
1012+ } else if(target_tid!=self_tid) {
1013+ /* wait for target to complete */
1014+ rtems_status_code sc = rtems_barrier_wait(v->join_barrier, RTEMS_NO_TIMEOUT);
1015+ if(sc!=RTEMS_SUCCESSFUL)
1016+ cantProceed("oopsj %s\n", rtems_status_text(sc));
1017+
1018+ if(sc != RTEMS_SUCCESSFUL) {
1019+ errlogPrintf("epicsThreadMustJoin('%s') -> %s\n", v->name, rtems_status_text(sc));
1020+ }
1021+ }
1022+
1023+ v->joinable = 0;
1024+ taskUnref(v);
1025+ /* target task may be deleted.
1026+ * self task is not deleted, even for self join.
1027+ */
1028+}
1029+
1030 void
1031 epicsThreadSuspendSelf (void)
1032 {
1033diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.h b/modules/libcom/src/osi/os/RTEMS/osdThread.h
1034index 4451f84..4eef8c0 100644
1035--- a/modules/libcom/src/osi/os/RTEMS/osdThread.h
1036+++ b/modules/libcom/src/osi/os/RTEMS/osdThread.h
1037@@ -3,10 +3,21 @@
1038 * National Laboratory.
1039 * Copyright (c) 2002 The Regents of the University of California, as
1040 * Operator of Los Alamos National Laboratory.
1041-* EPICS BASE Versions 3.13.7
1042-* and higher are distributed subject to a Software License Agreement found
1043-* in file LICENSE that is included with this distribution.
1044+* EPICS BASE is distributed subject to a Software License Agreement found
1045+* in file LICENSE that is included with this distribution.
1046 \*************************************************************************/
1047
1048+#ifndef INC_osdThread_H
1049+#define INC_osdThread_H
1050+
1051+#ifdef __cplusplus
1052+extern "C" {
1053+#endif
1054+
1055 int epicsThreadGetOssPriorityValue(unsigned int osiPriority);
1056
1057+#ifdef __cplusplus
1058+}
1059+#endif
1060+
1061+#endif /* INC_osdThread_H */
1062diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c
1063index 8cdb4a3..1cfa1ec 100644
1064--- a/modules/libcom/src/osi/os/WIN32/osdThread.c
1065+++ b/modules/libcom/src/osi/os/WIN32/osdThread.c
1066@@ -32,6 +32,7 @@
1067 #include "epicsAssert.h"
1068 #include "ellLib.h"
1069 #include "epicsExit.h"
1070+#include "epicsAtomic.h"
1071
1072 epicsShareFunc void osdThreadHooksRun(epicsThreadId id);
1073
1074@@ -46,6 +47,7 @@ typedef struct win32ThreadGlobal {
1075
1076 typedef struct epicsThreadOSD {
1077 ELLNODE node;
1078+ int refcnt;
1079 HANDLE handle;
1080 EPICSTHREADFUNC funptr;
1081 void * parm;
1082@@ -53,6 +55,7 @@ typedef struct epicsThreadOSD {
1083 DWORD id;
1084 unsigned epicsPriority;
1085 char isSuspended;
1086+ char joinable;
1087 } win32ThreadParam;
1088
1089 typedef struct epicsThreadPrivateOSD {
1090@@ -238,6 +241,8 @@ static void epicsParmCleanupWIN32 ( win32ThreadParam * pParm )
1091 }
1092
1093 if ( pParm ) {
1094+ if(epicsAtomicDecrIntT(&pParm->refcnt) > 0) return;
1095+
1096 /* fprintf ( stderr, "thread %s is exiting\n", pParm->pName ); */
1097 EnterCriticalSection ( & pGbl->mutex );
1098 ellDelete ( & pGbl->threadList, & pParm->node );
1099@@ -526,6 +531,7 @@ static win32ThreadParam * epicsThreadParmCreate ( const char *pName )
1100 pParmWIN32->pName = (char *) ( pParmWIN32 + 1 );
1101 strcpy ( pParmWIN32->pName, pName );
1102 pParmWIN32->isSuspended = 0;
1103+ epicsAtomicIncrIntT(&pParmWIN32->refcnt);
1104 }
1105 return pParmWIN32;
1106 }
1107@@ -579,11 +585,14 @@ static win32ThreadParam * epicsThreadImplicitCreate ( void )
1108 /*
1109 * epicsThreadCreate ()
1110 */
1111-epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName,
1112- unsigned int priority, unsigned int stackSize, EPICSTHREADFUNC pFunc,void *pParm)
1113+epicsThreadId epicsThreadCreateOpt (
1114+ const char * pName,
1115+ EPICSTHREADFUNC pFunc, void * pParm,
1116+ const epicsThreadOpts *opts )
1117 {
1118 win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal ();
1119 win32ThreadParam * pParmWIN32;
1120+ unsigned int stackSize;
1121 int osdPriority;
1122 DWORD wstat;
1123 BOOL bstat;
1124@@ -592,18 +601,26 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName,
1125 return NULL;
1126 }
1127
1128+ if (!opts) {
1129+ static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT;
1130+ opts = &opts_default;
1131+ }
1132+ stackSize = opts->stackSize;
1133+ if (stackSize <= epicsThreadStackBig)
1134+ stackSize = epicsThreadGetStackSize(stackSize);
1135+
1136 pParmWIN32 = epicsThreadParmCreate ( pName );
1137 if ( pParmWIN32 == 0 ) {
1138 return ( epicsThreadId ) pParmWIN32;
1139 }
1140 pParmWIN32->funptr = pFunc;
1141 pParmWIN32->parm = pParm;
1142- pParmWIN32->epicsPriority = priority;
1143+ pParmWIN32->epicsPriority = opts->priority;
1144
1145 {
1146 unsigned threadId;
1147 pParmWIN32->handle = (HANDLE) _beginthreadex (
1148- 0, stackSize, epicsWin32ThreadEntry,
1149+ 0, stackSize, epicsWin32ThreadEntry,
1150 pParmWIN32,
1151 CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION,
1152 & threadId );
1153@@ -615,7 +632,7 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName,
1154 pParmWIN32->id = ( DWORD ) threadId ;
1155 }
1156
1157- osdPriority = epicsThreadGetOsdPriorityValue (priority);
1158+ osdPriority = epicsThreadGetOsdPriorityValue (opts->priority);
1159 bstat = SetThreadPriority ( pParmWIN32->handle, osdPriority );
1160 if (!bstat) {
1161 CloseHandle ( pParmWIN32->handle );
1162@@ -637,9 +654,48 @@ epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate (const char *pName,
1163 return NULL;
1164 }
1165
1166+ if(opts->joinable) {
1167+ pParmWIN32->joinable = 1;
1168+ epicsAtomicIncrIntT(&pParmWIN32->refcnt);
1169+ }
1170+
1171 return ( epicsThreadId ) pParmWIN32;
1172 }
1173
1174+void epicsThreadMustJoin(epicsThreadId id)
1175+{
1176+ win32ThreadParam * pParmWIN32 = id;
1177+
1178+ if(!id) {
1179+ /* no-op */
1180+ } else if(!pParmWIN32->joinable) {
1181+ if(epicsThreadGetIdSelf()==id) {
1182+ fprintf(stderr, "Warning: %s thread self-join of unjoinable\n", pParmWIN32->pName);
1183+
1184+ } else {
1185+ /* try to error nicely, however in all likelyhood de-ref of
1186+ * 'id' has already caused SIGSEGV as we are racing thread exit,
1187+ * which free's 'id'.
1188+ */
1189+ cantProceed("Error: %s thread not joinable.\n", pParmWIN32->pName);
1190+ }
1191+ return;
1192+
1193+ } else if(epicsThreadGetIdSelf() != id) {
1194+ DWORD status = WaitForSingleObject(pParmWIN32->handle, INFINITE);
1195+ if(status != WAIT_OBJECT_0) {
1196+ /* TODO: signal error? */
1197+ }
1198+
1199+ pParmWIN32->joinable = 0;
1200+ epicsParmCleanupWIN32(pParmWIN32);
1201+ } else {
1202+ /* join self silently does nothing */
1203+ pParmWIN32->joinable = 0;
1204+ epicsParmCleanupWIN32(pParmWIN32);
1205+ }
1206+}
1207+
1208 /*
1209 * epicsThreadSuspendSelf ()
1210 */
1211diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.h b/modules/libcom/src/osi/os/WIN32/osdThread.h
1212index 136e96b..69bc364 100644
1213--- a/modules/libcom/src/osi/os/WIN32/osdThread.h
1214+++ b/modules/libcom/src/osi/os/WIN32/osdThread.h
1215@@ -3,13 +3,11 @@
1216 * National Laboratory.
1217 * Copyright (c) 2002 The Regents of the University of California, as
1218 * Operator of Los Alamos National Laboratory.
1219-* EPICS BASE Versions 3.13.7
1220-* and higher are distributed subject to a Software License Agreement found
1221-* in file LICENSE that is included with this distribution.
1222+* EPICS BASE is distributed subject to a Software License Agreement found
1223+* in file LICENSE that is included with this distribution.
1224 \*************************************************************************/
1225
1226 #ifndef osdThreadh
1227 #define osdThreadh
1228
1229-
1230 #endif /* osdThreadh */
1231diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c
1232index e3e0fe3..a13d387 100644
1233--- a/modules/libcom/src/osi/os/posix/osdThread.c
1234+++ b/modules/libcom/src/osi/os/posix/osdThread.c
1235@@ -38,6 +38,7 @@
1236 #include "errlog.h"
1237 #include "epicsAssert.h"
1238 #include "epicsExit.h"
1239+#include "epicsAtomic.h"
1240
1241 epicsShareFunc void epicsThreadShowInfo(epicsThreadOSD *pthreadInfo, unsigned int level);
1242 epicsShareFunc void osdThreadHooksRun(epicsThreadId id);
1243@@ -167,12 +168,14 @@ static epicsThreadOSD * create_threadInfo(const char *name)
1244 return NULL;
1245 }
1246 strcpy(pthreadInfo->name, name);
1247+ epicsAtomicIncrIntT(&pthreadInfo->refcnt); /* initial ref for the thread itself */
1248 return pthreadInfo;
1249 }
1250
1251 static epicsThreadOSD * init_threadInfo(const char *name,
1252 unsigned int priority, unsigned int stackSize,
1253- EPICSTHREADFUNC funptr,void *parm)
1254+ EPICSTHREADFUNC funptr,void *parm,
1255+ unsigned joinable)
1256 {
1257 epicsThreadOSD *pthreadInfo;
1258 int status;
1259@@ -182,12 +185,15 @@ static epicsThreadOSD * init_threadInfo(const char *name,
1260 return NULL;
1261 pthreadInfo->createFunc = funptr;
1262 pthreadInfo->createArg = parm;
1263+ pthreadInfo->joinable = joinable;
1264 status = pthread_attr_init(&pthreadInfo->attr);
1265 checkStatusOnce(status,"pthread_attr_init");
1266 if(status) return 0;
1267- status = pthread_attr_setdetachstate(
1268- &pthreadInfo->attr, PTHREAD_CREATE_DETACHED);
1269- checkStatusOnce(status,"pthread_attr_setdetachstate");
1270+ if(!joinable){
1271+ status = pthread_attr_setdetachstate(
1272+ &pthreadInfo->attr, PTHREAD_CREATE_DETACHED);
1273+ checkStatusOnce(status,"pthread_attr_setdetachstate");
1274+ }
1275 #if defined (_POSIX_THREAD_ATTR_STACKSIZE)
1276 #if ! defined (OSITHREAD_USE_DEFAULT_STACK)
1277 status = pthread_attr_setstacksize( &pthreadInfo->attr,(size_t)stackSize);
1278@@ -204,6 +210,8 @@ static void free_threadInfo(epicsThreadOSD *pthreadInfo)
1279 {
1280 int status;
1281
1282+ if(epicsAtomicDecrIntT(&pthreadInfo->refcnt) > 0) return;
1283+
1284 status = mutexLock(&listLock);
1285 checkStatusQuit(status,"pthread_mutex_lock","free_threadInfo");
1286 if(pthreadInfo->isOnThreadList) ellDelete(&pthreadList,&pthreadInfo->node);
1287@@ -366,7 +374,7 @@ static void once(void)
1288 if(errVerbose) fprintf(stderr,"task priorities are not implemented\n");
1289 #endif /* _POSIX_THREAD_PRIORITY_SCHEDULING */
1290
1291- pthreadInfo = init_threadInfo("_main_",0,epicsThreadGetStackSize(epicsThreadStackSmall),0,0);
1292+ pthreadInfo = init_threadInfo("_main_",0,epicsThreadGetStackSize(epicsThreadStackSmall),0,0,0);
1293 assert(pthreadInfo!=NULL);
1294 status = pthread_setspecific(getpthreadInfo,(void *)pthreadInfo);
1295 checkStatusOnceQuit(status,"pthread_setspecific","epicsThreadInit");
1296@@ -445,15 +453,22 @@ void epicsThreadRealtimeLock(void)
1297 #endif
1298 }
1299
1300-epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass)
1301-{
1302 #if defined (OSITHREAD_USE_DEFAULT_STACK)
1303- return 0;
1304+#define STACK_SIZE(f) (0)
1305 #elif defined(_POSIX_THREAD_ATTR_STACKSIZE) && _POSIX_THREAD_ATTR_STACKSIZE > 0
1306 #define STACK_SIZE(f) (f * 0x10000 * sizeof(void *))
1307 static const unsigned stackSizeTable[epicsThreadStackBig+1] = {
1308 STACK_SIZE(1), STACK_SIZE(2), STACK_SIZE(4)
1309 };
1310+#else
1311+#define STACK_SIZE(f) (0)
1312+#endif /*_POSIX_THREAD_ATTR_STACKSIZE*/
1313+
1314+epicsShareFunc unsigned int epicsShareAPI epicsThreadGetStackSize (epicsThreadStackSizeClass stackSizeClass)
1315+{
1316+#if defined (OSITHREAD_USE_DEFAULT_STACK)
1317+ return 0;
1318+#elif defined(_POSIX_THREAD_ATTR_STACKSIZE) && _POSIX_THREAD_ATTR_STACKSIZE > 0
1319 if (stackSizeClass<epicsThreadStackSmall) {
1320 errlogPrintf("epicsThreadGetStackSize illegal argument (too small)");
1321 return stackSizeTable[epicsThreadStackBig];
1322@@ -511,42 +526,66 @@ epicsShareFunc void epicsShareAPI epicsThreadOnce(epicsThreadOnceId *id, void (*
1323 checkStatusQuit(status,"pthread_mutex_unlock","epicsThreadOnce");
1324 }
1325
1326-epicsShareFunc epicsThreadId epicsShareAPI epicsThreadCreate(const char *name,
1327- unsigned int priority, unsigned int stackSize,
1328- EPICSTHREADFUNC funptr,void *parm)
1329+epicsThreadId
1330+epicsThreadCreateOpt(const char * name,
1331+ EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts )
1332 {
1333+ unsigned int stackSize;
1334 epicsThreadOSD *pthreadInfo;
1335 int status;
1336 sigset_t blockAllSig, oldSig;
1337
1338 epicsThreadInit();
1339 assert(pcommonAttr);
1340+
1341+ if (!opts) {
1342+ static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT;
1343+ opts = &opts_default;
1344+ }
1345+ stackSize = opts->stackSize;
1346+ if (stackSize <= epicsThreadStackBig)
1347+ stackSize = epicsThreadGetStackSize(stackSize);
1348+
1349 sigfillset(&blockAllSig);
1350- pthread_sigmask(SIG_SETMASK,&blockAllSig,&oldSig);
1351- pthreadInfo = init_threadInfo(name,priority,stackSize,funptr,parm);
1352- if(pthreadInfo==0) return 0;
1353+ pthread_sigmask(SIG_SETMASK, &blockAllSig, &oldSig);
1354+
1355+ pthreadInfo = init_threadInfo(name, opts->priority, stackSize, funptr,
1356+ parm, opts->joinable);
1357+ if (pthreadInfo==0)
1358+ return 0;
1359+
1360 pthreadInfo->isEpicsThread = 1;
1361- setSchedulingPolicy(pthreadInfo,SCHED_FIFO);
1362+ setSchedulingPolicy(pthreadInfo, SCHED_FIFO);
1363 pthreadInfo->isRealTimeScheduled = 1;
1364- status = pthread_create(&pthreadInfo->tid,&pthreadInfo->attr,
1365- start_routine,pthreadInfo);
1366- if(status==EPERM){
1367+
1368+ status = pthread_create(&pthreadInfo->tid, &pthreadInfo->attr,
1369+ start_routine, pthreadInfo);
1370+ if (status==EPERM) {
1371 /* Try again without SCHED_FIFO*/
1372 free_threadInfo(pthreadInfo);
1373- pthreadInfo = init_threadInfo(name,priority,stackSize,funptr,parm);
1374- if(pthreadInfo==0) return 0;
1375+
1376+ pthreadInfo = init_threadInfo(name, opts->priority, stackSize,
1377+ funptr, parm, opts->joinable);
1378+ if (pthreadInfo==0)
1379+ return 0;
1380+
1381 pthreadInfo->isEpicsThread = 1;
1382- status = pthread_create(&pthreadInfo->tid,&pthreadInfo->attr,
1383- start_routine,pthreadInfo);
1384+ status = pthread_create(&pthreadInfo->tid, &pthreadInfo->attr,
1385+ start_routine, pthreadInfo);
1386 }
1387- checkStatusOnce(status,"pthread_create");
1388- if(status) {
1389+ checkStatusOnce(status, "pthread_create");
1390+ if (status) {
1391 free_threadInfo(pthreadInfo);
1392 return 0;
1393 }
1394- status = pthread_sigmask(SIG_SETMASK,&oldSig,NULL);
1395- checkStatusOnce(status,"pthread_sigmask");
1396- return(pthreadInfo);
1397+
1398+ status = pthread_sigmask(SIG_SETMASK, &oldSig, NULL);
1399+ checkStatusOnce(status, "pthread_sigmask");
1400+ if (pthreadInfo->joinable) {
1401+ /* extra ref for epicsThreadMustJoin() */
1402+ epicsAtomicIncrIntT(&pthreadInfo->refcnt);
1403+ }
1404+ return pthreadInfo;
1405 }
1406
1407 /*
1408@@ -584,7 +623,40 @@ static epicsThreadOSD *createImplicit(void)
1409 }
1410 return pthreadInfo;
1411 }
1412-
1413
1414+
1415+void epicsThreadMustJoin(epicsThreadId id)
1416+{
1417+ void *ret = NULL;
1418+ int status;
1419+
1420+ if(!id) {
1421+ return;
1422+ } else if(!id->joinable) {
1423+ if(epicsThreadGetIdSelf()==id) {
1424+ errlogPrintf("Warning: %s thread self-join of unjoinable\n", id->name);
1425+
1426+ } else {
1427+ /* try to error nicely, however in all likelyhood de-ref of
1428+ * 'id' has already caused SIGSEGV as we are racing thread exit,
1429+ * which free's 'id'.
1430+ */
1431+ cantProceed("Error: %s thread not joinable.\n", id->name);
1432+ }
1433+ return;
1434+ }
1435+
1436+ status = pthread_join(id->tid, &ret);
1437+ if(status == EDEADLK) {
1438+ /* Thread can't join itself (directly or indirectly)
1439+ * so we detach instead.
1440+ */
1441+ status = pthread_detach(id->tid);
1442+ checkStatusOnce(status, "pthread_detach");
1443+ } else checkStatusOnce(status, "pthread_join");
1444+ id->joinable = 0;
1445+ free_threadInfo(id);
1446+}
1447+
1448 epicsShareFunc void epicsShareAPI epicsThreadSuspendSelf(void)
1449 {
1450 epicsThreadOSD *pthreadInfo;
1451diff --git a/modules/libcom/src/osi/os/posix/osdThread.h b/modules/libcom/src/osi/os/posix/osdThread.h
1452index 3a80b53..8fe8f14 100644
1453--- a/modules/libcom/src/osi/os/posix/osdThread.h
1454+++ b/modules/libcom/src/osi/os/posix/osdThread.h
1455@@ -3,9 +3,8 @@
1456 * National Laboratory.
1457 * Copyright (c) 2002 The Regents of the University of California, as
1458 * Operator of Los Alamos National Laboratory.
1459-* EPICS BASE Versions 3.13.7
1460-* and higher are distributed subject to a Software License Agreement found
1461-* in file LICENSE that is included with this distribution.
1462+* EPICS BASE is distributed subject to a Software License Agreement found
1463+* in file LICENSE that is included with this distribution.
1464 \*************************************************************************/
1465 #ifndef osdThreadh
1466 #define osdThreadh
1467@@ -22,6 +21,7 @@ extern "C" {
1468
1469 typedef struct epicsThreadOSD {
1470 ELLNODE node;
1471+ int refcnt;
1472 pthread_t tid;
1473 pthread_attr_t attr;
1474 struct sched_param schedParam;
1475@@ -34,6 +34,7 @@ typedef struct epicsThreadOSD {
1476 int isRealTimeScheduled;
1477 int isOnThreadList;
1478 unsigned int osiPriority;
1479+ int joinable;
1480 char name[1]; /* actually larger */
1481 } epicsThreadOSD;
1482
1483diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c
1484index ce01ea6..0ed3138 100644
1485--- a/modules/libcom/src/osi/os/vxWorks/osdThread.c
1486+++ b/modules/libcom/src/osi/os/vxWorks/osdThread.c
1487@@ -34,6 +34,22 @@
1488 #include "vxLib.h"
1489 #include "epicsExit.h"
1490
1491+#ifdef EPICS_THREAD_CAN_JOIN
1492+ /* The implementation of epicsThreadMustJoin() here uses 2 features
1493+ * of VxWorks that were first introduced in VxWorks 6.9: taskWait(),
1494+ * and the taskSpareFieldGet/Set routines in taskUtilLib.
1495+ */
1496+ #include <taskUtilLib.h>
1497+
1498+ #define JOIN_WARNING_TIMEOUT (60 * sysClkRateGet())
1499+
1500+ static SPARE_NUM joinField;
1501+ #define ALLOT_JOIN(tid) taskSpareNumAllot(tid, &joinField)
1502+#else
1503+ #define ALLOT_JOIN(tid)
1504+#endif
1505+
1506+
1507 epicsShareFunc void osdThreadHooksRun(epicsThreadId id);
1508
1509 #if CPU_FAMILY == MC680X0
1510@@ -109,6 +125,7 @@ static void epicsThreadInit(void)
1511 assert(taskIdList);
1512 taskIdListSize = ID_LIST_CHUNK;
1513 atRebootRegister();
1514+ ALLOT_JOIN(0);
1515 done = 1;
1516 }
1517 lock = 0;
1518@@ -170,19 +187,58 @@ void epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg)
1519 }
1520 semGive(epicsThreadOnceMutex);
1521 }
1522-
1523
1524+
1525+#ifdef EPICS_THREAD_CAN_JOIN
1526+
1527+/* This routine is not static so it appears in the back-trace
1528+ * of a thread that is waiting to be joined.
1529+ */
1530+void epicsThreadAwaitingJoin(int tid)
1531+{
1532+ SEM_ID joinSem = (SEM_ID) taskSpareFieldGet(tid, joinField);
1533+ STATUS status;
1534+
1535+ if (!joinSem || (int) joinSem == ERROR)
1536+ return;
1537+
1538+ /* Wait for our supervisor */
1539+ status = semTake(joinSem, JOIN_WARNING_TIMEOUT);
1540+ if (status && errno == S_objLib_OBJ_TIMEOUT) {
1541+ errlogPrintf("Warning: epicsThread '%s' still awaiting join\n",
1542+ epicsThreadGetNameSelf());
1543+ status = semTake(joinSem, WAIT_FOREVER);
1544+ }
1545+ if (status)
1546+ perror("epicsThreadAwaitingJoin");
1547+
1548+ semDelete(joinSem);
1549+ taskSpareFieldSet(tid, joinField, 0);
1550+}
1551+ #define PREPARE_JOIN(tid, joinable) \
1552+ taskSpareFieldSet(tid, joinField, \
1553+ joinable ? (int) semBCreate(SEM_Q_FIFO, SEM_EMPTY) : 0)
1554+ #define AWAIT_JOIN(tid) epicsThreadAwaitingJoin(tid)
1555+#else
1556+ #define PREPARE_JOIN(tid, joinable)
1557+ #define AWAIT_JOIN(tid)
1558+#endif
1559+
1560 static void createFunction(EPICSTHREADFUNC func, void *parm)
1561 {
1562 int tid = taskIdSelf();
1563
1564 taskVarAdd(tid,(int *)(char *)&papTSD);
1565- /*Make sure that papTSD is still 0 after that call to taskVarAdd*/
1566- papTSD = 0;
1567+ papTSD = NULL; /* Initialize for this thread */
1568+
1569 osdThreadHooksRun((epicsThreadId)tid);
1570+
1571 (*func)(parm);
1572+
1573 epicsExitCallAtThreadExits ();
1574 free(papTSD);
1575 taskVarDelete(tid,(int *)(char *)&papTSD);
1576+
1577+ AWAIT_JOIN(tid);
1578 }
1579
1580 #ifdef ALTIVEC
1581@@ -190,27 +246,101 @@ static void createFunction(EPICSTHREADFUNC func, void *parm)
1582 #else
1583 #define TASK_FLAGS (VX_FP_TASK)
1584 #endif
1585-epicsThreadId epicsThreadCreate(const char *name,
1586- unsigned int priority, unsigned int stackSize,
1587- EPICSTHREADFUNC funptr,void *parm)
1588+epicsThreadId epicsThreadCreateOpt(const char * name,
1589+ EPICSTHREADFUNC funptr, void * parm, const epicsThreadOpts *opts )
1590 {
1591+ unsigned int stackSize;
1592 int tid;
1593
1594 epicsThreadInit();
1595- if(stackSize<100) {
1596- errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n",name,stackSize);
1597- return(0);
1598+
1599+ if (!opts) {
1600+ static const epicsThreadOpts opts_default = EPICS_THREAD_OPTS_INIT;
1601+ opts = &opts_default;
1602 }
1603- tid = taskSpawn((char *)name,getOssPriorityValue(priority),
1604+ stackSize = opts->stackSize;
1605+ if (stackSize <= epicsThreadStackBig)
1606+ stackSize = epicsThreadGetStackSize(stackSize);
1607+
1608+ if (stackSize < 100) {
1609+ errlogPrintf("epicsThreadCreate %s illegal stackSize %d\n",
1610+ name, stackSize);
1611+ return 0;
1612+ }
1613+
1614+ tid = taskCreate((char *)name,getOssPriorityValue(opts->priority),
1615 TASK_FLAGS, stackSize,
1616- (FUNCPTR)createFunction,(int)funptr,(int)parm,
1617+ (FUNCPTR)createFunction, (int)funptr, (int)parm,
1618 0,0,0,0,0,0,0,0);
1619- if(tid==ERROR) {
1620+ if (tid == ERROR) {
1621 errlogPrintf("epicsThreadCreate %s failure %s\n",
1622- name,strerror(errno));
1623- return(0);
1624+ name, strerror(errno));
1625+ return 0;
1626 }
1627- return((epicsThreadId)tid);
1628+
1629+ PREPARE_JOIN(tid, opts->joinable);
1630+ taskActivate(tid);
1631+
1632+ return (epicsThreadId)tid;
1633+}
1634+
1635+void epicsThreadMustJoin(epicsThreadId id)
1636+{
1637+#ifdef EPICS_THREAD_CAN_JOIN
1638+ const char *fn = "epicsThreadMustJoin";
1639+ int tid = (int) id;
1640+ SEM_ID joinSem;
1641+ STATUS status;
1642+
1643+ if (!tid)
1644+ return;
1645+
1646+ joinSem = (SEM_ID) taskSpareFieldGet(tid, joinField);
1647+ if ((int) joinSem == ERROR) {
1648+ errlogPrintf("%s: Thread '%s' no longer exists.\n",
1649+ fn, taskName(tid));
1650+ return;
1651+ }
1652+
1653+ if (tid == taskIdSelf()) {
1654+ if (joinSem) {
1655+ semDelete(joinSem);
1656+ taskSpareFieldSet(tid, joinField, 0);
1657+ }
1658+ else {
1659+ errlogPrintf("%s: Self-join of unjoinable thread '%s'\n",
1660+ fn, taskName(tid));
1661+ }
1662+ return;
1663+ }
1664+
1665+ if (!joinSem) {
1666+ cantProceed("%s: Thread '%s' is not joinable.\n",
1667+ fn, taskName(tid));
1668+ return;
1669+ }
1670+
1671+ semGive(joinSem); /* Rendezvous with thread */
1672+
1673+ status = taskWait(tid, JOIN_WARNING_TIMEOUT);
1674+ if (status && errno == S_objLib_OBJ_TIMEOUT) {
1675+ errlogPrintf("Warning: %s still waiting for thread '%s'\n",
1676+ fn, taskName(tid));
1677+ status = taskWait(tid, WAIT_FOREVER);
1678+ }
1679+ if (status) {
1680+ if (errno == S_taskLib_ILLEGAL_OPERATION) {
1681+ errlogPrintf("%s: This shouldn't happen!\n", fn);
1682+ }
1683+ else if (errno == S_objLib_OBJ_ID_ERROR) {
1684+ errlogPrintf("%s: %x is not a known thread\n", fn, tid);
1685+ }
1686+ else {
1687+ perror(fn);
1688+ }
1689+ cantProceed(fn);
1690+ }
1691+#endif
1692 }
1693
1694 void epicsThreadSuspendSelf()
1695diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h
1696index 2ee9f2d..1514566 100644
1697--- a/modules/libcom/src/osi/os/vxWorks/osdThread.h
1698+++ b/modules/libcom/src/osi/os/vxWorks/osdThread.h
1699@@ -3,11 +3,17 @@
1700 * National Laboratory.
1701 * Copyright (c) 2002 The Regents of the University of California, as
1702 * Operator of Los Alamos National Laboratory.
1703-* EPICS BASE Versions 3.13.7
1704-* and higher are distributed subject to a Software License Agreement found
1705-* in file LICENSE that is included with this distribution.
1706+* EPICS BASE is distributed subject to a Software License Agreement found
1707+* in file LICENSE that is included with this distribution.
1708 \*************************************************************************/
1709+
1710 #ifndef osdThreadh
1711 #define osdThreadh
1712
1713+/* VxWorks 6.9 and later can support joining threads */
1714+
1715+#if (_WRS_VXWORKS_MAJOR == 6 && _WRS_VXWORKS_MINOR < 9)
1716+#undef EPICS_THREAD_CAN_JOIN
1717+#endif
1718+
1719 #endif /* osdThreadh */
1720diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp
1721index eb26cc8..4796438 100644
1722--- a/modules/libcom/test/epicsThreadTest.cpp
1723+++ b/modules/libcom/test/epicsThreadTest.cpp
1724@@ -18,11 +18,14 @@
1725 #include <math.h>
1726
1727 #include "epicsThread.h"
1728+#include "epicsEvent.h"
1729 #include "epicsTime.h"
1730 #include "errlog.h"
1731 #include "epicsUnitTest.h"
1732 #include "testMain.h"
1733
1734+namespace {
1735+
1736 static epicsThreadPrivate<int> privateKey;
1737
1738 class myThread: public epicsThreadRunable {
1739@@ -37,7 +40,7 @@ private:
1740 };
1741
1742 myThread::myThread(int arg,const char *name) :
1743- thread(*this,name,epicsThreadGetStackSize(epicsThreadStackSmall),50+arg),
1744+ thread(*this,name,epicsThreadStackSmall,50+arg),
1745 argvalue(0)
1746 {
1747 argvalue = new int;
1748@@ -52,7 +55,7 @@ void myThread::run()
1749 startEvt.signal();
1750 int *pset = argvalue;
1751 privateKey.set(argvalue);
1752- epicsThreadSleep(2.0);
1753+
1754 int *pget = privateKey.get();
1755 testOk1(pget == pset);
1756
1757@@ -60,9 +63,113 @@ void myThread::run()
1758 testOk1(thread.getPriority() == epicsThreadGetPriority(self));
1759 }
1760
1761+void testMyThread()
1762+{
1763+
1764+ const int ntasks = 3;
1765+ myThread *myThreads[ntasks];
1766+
1767+ int startPriority = 0;
1768+ for (int i = 0; i < ntasks; i++) {
1769+ char name[10];
1770+ sprintf(name, "t%d", i);
1771+ myThreads[i] = new myThread(i, name);
1772+ if (i == 0)
1773+ startPriority = myThreads[i]->thread.getPriority();
1774+ myThreads[i]->thread.setPriority(startPriority + i);
1775+ }
1776+
1777+ for (int i = 0; i < ntasks; i++) {
1778+ myThreads[i]->startEvt.wait();
1779+ myThreads[i]->thread.exitWait();
1780+ delete myThreads[i];
1781+ }
1782+}
1783+
1784+struct joinStuff {
1785+ epicsThreadOpts *opts;
1786+ epicsEvent *trigger;
1787+ epicsEvent *finished;
1788+};
1789+
1790+void donothing(void *arg)
1791+{}
1792+
1793+void dowait(void *arg)
1794+{
1795+ epicsEvent *trigger = (epicsEvent *) arg;
1796+ trigger->wait();
1797+ epicsThreadSleep(0.1);
1798+}
1799+
1800+void dodelay(void *arg)
1801+{
1802+ epicsThreadSleep(2.0);
1803+}
1804+
1805+void joinTests(void *arg)
1806+{
1807+ struct joinStuff *stuff = (struct joinStuff *) arg;
1808+
1809+ // Task finishes before parent joins
1810+ epicsThreadId tid = epicsThreadCreateOpt("nothing",
1811+ &donothing, 0, stuff->opts);
1812+ epicsThreadSleep(0.1);
1813+ epicsThreadMustJoin(tid);
1814+
1815+ // Parent joins before task finishes
1816+ tid = epicsThreadCreateOpt("await",
1817+ &dowait, stuff->trigger, stuff->opts);
1818+ stuff->trigger->signal();
1819+ epicsThreadMustJoin(tid);
1820+
1821+ // Parent gets delayed until task finishes
1822+ epicsTime start, end;
1823+ start = epicsTime::getCurrent();
1824+ tid = epicsThreadCreateOpt("delay",
1825+ &dodelay, 0, stuff->opts);
1826+ epicsThreadMustJoin(tid);
1827+ end = epicsTime::getCurrent();
1828+ double duration = end - start;
1829+#ifndef EPICS_THREAD_CAN_JOIN
1830+ testTodoBegin("Thread join doesn't work");
1831+#endif
1832+ testOk(duration > 1.0, "Join delayed parent (%g seconds)", duration);
1833+ testTodoEnd();
1834+
1835+ // This is a no-op
1836+ epicsThreadId self = epicsThreadGetIdSelf();
1837+ epicsThreadMustJoin(self);
1838+
1839+ // This is a no-op as well, except for a warning.
1840+ eltc(0);
1841+ epicsThreadMustJoin(self);
1842+ eltc(1);
1843+
1844+ stuff->finished->signal();
1845+}
1846+
1847+void testJoining()
1848+{
1849+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
1850+ epicsEvent finished, trigger;
1851+ struct joinStuff stuff = {
1852+ &opts, &trigger, &finished
1853+ };
1854+
1855+ opts.priority = 50;
1856+ opts.joinable = 1;
1857+ epicsThreadCreateOpt("parent", &joinTests, &stuff, &opts);
1858+
1859+ // as selfjoin joins itself, we can't.
1860+ testOk(finished.wait(10.0), "Join tests completed");
1861+}
1862+
1863+} // namespace
1864
1865 typedef struct info {
1866 int isOkToBlock;
1867+ int didSomething;
1868 } info;
1869
1870 extern "C" {
1871@@ -71,52 +178,55 @@ static void thread(void *arg)
1872 info *pinfo = (info *)arg;
1873
1874 epicsThreadSetOkToBlock(pinfo->isOkToBlock);
1875- epicsThreadSleep(1.0);
1876
1877 testOk(epicsThreadIsOkToBlock() == pinfo->isOkToBlock,
1878 "%s epicsThreadIsOkToBlock() = %d",
1879 epicsThreadGetNameSelf(), pinfo->isOkToBlock);
1880- epicsThreadSleep(0.1);
1881+
1882+ pinfo->didSomething = 1;
1883 }
1884 }
1885
1886-
1887-MAIN(epicsThreadTest)
1888+static void testOkToBlock()
1889 {
1890- testPlan(9);
1891+ epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
1892
1893- unsigned int ncpus = epicsThreadGetCPUs();
1894- testDiag("System has %u CPUs", ncpus);
1895- testOk1(ncpus > 0);
1896+ opts.priority = 50;
1897+ opts.joinable = 1;
1898
1899- const int ntasks = 3;
1900- myThread *myThreads[ntasks];
1901+ info infoA = {0, 0};
1902+ epicsThreadId threadA = epicsThreadCreateOpt("threadA", thread, &infoA, &opts);
1903
1904- int startPriority = 0;
1905- for (int i = 0; i < ntasks; i++) {
1906- char name[10];
1907- sprintf(name, "t%d", i);
1908- myThreads[i] = new myThread(i, name);
1909- if (i == 0)
1910- startPriority = myThreads[i]->thread.getPriority();
1911- myThreads[i]->thread.setPriority(startPriority + i);
1912- }
1913+ info infoB = {1, 0};
1914+ epicsThreadId threadB = epicsThreadCreateOpt("threadB", thread, &infoB, &opts);
1915
1916- for (int i = 0; i < ntasks; i++) {
1917- myThreads[i]->startEvt.wait();
1918- myThreads[i]->thread.exitWait();
1919- delete myThreads[i];
1920- }
1921+ // join B first to better our chance of detecting if it never runs.
1922+ epicsThreadMustJoin(threadB);
1923+ testOk1(infoB.didSomething);
1924
1925- unsigned int stackSize = epicsThreadGetStackSize(epicsThreadStackSmall);
1926+ epicsThreadMustJoin(threadA);
1927+ testOk1(infoA.didSomething);
1928+}
1929
1930- info infoA = {0};
1931- epicsThreadCreate("threadA", 50, stackSize, thread, &infoA);
1932
1933- info infoB = {1};
1934- epicsThreadCreate("threadB", 50, stackSize, thread, &infoB);
1935+MAIN(epicsThreadTest)
1936+{
1937+ testPlan(13);
1938
1939- epicsThreadSleep(2.0);
1940+ unsigned int ncpus = epicsThreadGetCPUs();
1941+ testDiag("System has %u CPUs", ncpus);
1942+ testOk1(ncpus > 0);
1943+ testDiag("main() thread %p", epicsThreadGetIdSelf());
1944+
1945+ testJoining(); // Do this first, ~epicsThread() uses it...
1946+ testMyThread();
1947+ testOkToBlock();
1948+
1949+ // attempt to self-join from a non-EPICS thread
1950+ // to make sure it does nothing as expected
1951+ eltc(0);
1952+ epicsThreadMustJoin(epicsThreadGetIdSelf());
1953+ eltc(1);
1954
1955 return testDone();
1956 }

Subscribers

People subscribed via source and target branches