Merge ~epics-core/epics-base/+git/Com:thread-join into ~epics-core/epics-base/+git/epics-base:7.0
- Git
- lp:~epics-core/epics-base/+git/Com
- thread-join
- Merge into 7.0
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
mdavidsaver | Approve | ||
Review via email: mp+361379@code.launchpad.net |
Commit message
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 epicsThreadCrea
The epicsThread class is modified to use epicsThreadJoin() automatically.
Implemented for all targets except vxWorks where epicsThreadJoin() is a no-op and EPICS_THREAD_
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.
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 epicsThreadMust
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_
Looking at the Posix implementation, a joinable thread that calls epicsThreadJoin
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.
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:/
> EPICS_THREAD_
This will go away now.
> epicsThreadJoin
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.
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 epicsThreadMust
VxWorks 6.8 and earlier still define EPICS_THREAD_
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
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.
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.
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 epicsThreadMust
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:/
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.
mdavidsaver (mdavidsaver) wrote : | # |
> But there is a cure: https:/
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_
> 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 epicsMutexLockE
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.
Martin Konrad (info-martin-konrad) wrote : | # |
The problem I have with this is that
// ...
t1.joinWithTime
// 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.joinWithTi
} // 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.
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.
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.
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(
// 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.
Andrew Johnson (anj) wrote : | # |
The name epicsThreadMust
I would expect a routine called epicsThreadTryJ
Might pthreads allow us to write our own equivalent to pthread_
mdavidsaver (mdavidsaver) wrote : | # |
Ok, I think we are getting side tracked. I will renamed epicsThreadJoin() -> epicsThreadMust
wrt. epicsThreadMust
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.
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_
Routine is not callable from an ISR.
S_taskLib_
Cannot wait on self
S_objLib_
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_
Timeout occured while pending on sempahore.
S_objLib_
Would have blocked but NO_WAIT was specified.
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.
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.
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 epicsThreadMust
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 epicsThreadCrea
mdavidsaver (mdavidsaver) wrote : | # |
Various changes, including the promised rename. I've also used epicsThreadMust
mdavidsaver (mdavidsaver) wrote : | # |
Was forgetting the vxworks issue
Andrew Johnson (anj) wrote : | # |
AI on MAD to document semantics of epicsThreadJoin().
AI on AJ to finish VxWorks implementation.
Andrew Johnson (anj) wrote : | # |
A working VxWorks 6.9+ implementation of epicsThreadMust
The changes made to asCaStop, dbCa and dbEvent mean the IOC now requires EPICS_THREAD_
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 epicsThreadMust
Could we replace the epicsThreadOpts
epicsThreadOpts opts;
epicsThread
into this:
epicsThreadOpts opts = EPICS_THREAD_
which removes the need for that epicsThreadOpts
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 epicsThreadCrea
mdavidsaver (mdavidsaver) wrote : | # |
Thanks Andrew.
> IOC now requires EPICS_THREAD_
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_
That said, I'm happy to be rid of EPICS_THREAD_
> 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_
Also no objection.
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 epicsThreadMust
I have significantly adjusted and expanded the Release Notes again.
I have changed the EPICS_THREAD_
> ... EPICS_THREAD_
Done. External code can now use #ifdef EPICS_THREAD_
I just added another test to epicsThreadTest.cpp to check that a join actually delays the caller; the empty epicsThreadMust
I'm happy with this now, others may wish to take a final look given my recent updates.
mdavidsaver (mdavidsaver) wrote : | # |
> the shutdown logic changes in dbEvent.c
This is in part reverting d2b0e920012d4c9
> 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:/
Preview Diff
1 | diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html |
2 | index 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", &threadMain, NULL, &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 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 | |
103 | diff --git a/modules/database/src/ioc/as/asCa.c b/modules/database/src/ioc/as/asCa.c |
104 | index 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);} |
149 | diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c |
150 | index 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 | |
192 | diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c |
193 | index 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 | } |
350 | diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp |
351 | index 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 | } |
425 | diff --git a/modules/libcom/RTEMS/rtems_config.c b/modules/libcom/RTEMS/rtems_config.c |
426 | index 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) |
437 | diff --git a/modules/libcom/src/osi/epicsThread.cpp b/modules/libcom/src/osi/epicsThread.cpp |
438 | index 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 | } |
510 | diff --git a/modules/libcom/src/osi/epicsThread.h b/modules/libcom/src/osi/epicsThread.h |
511 | index 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 & ); |
779 | diff --git a/modules/libcom/src/osi/os/Linux/osdThread.h b/modules/libcom/src/osi/os/Linux/osdThread.h |
780 | index 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 | |
799 | diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.c b/modules/libcom/src/osi/os/RTEMS/osdThread.c |
800 | index 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, ¬e); |
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 | { |
1033 | diff --git a/modules/libcom/src/osi/os/RTEMS/osdThread.h b/modules/libcom/src/osi/os/RTEMS/osdThread.h |
1034 | index 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 */ |
1062 | diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c |
1063 | index 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 | */ |
1211 | diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.h b/modules/libcom/src/osi/os/WIN32/osdThread.h |
1212 | index 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 */ |
1231 | diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c |
1232 | index 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; |
1451 | diff --git a/modules/libcom/src/osi/os/posix/osdThread.h b/modules/libcom/src/osi/os/posix/osdThread.h |
1452 | index 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 | |
1483 | diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.c b/modules/libcom/src/osi/os/vxWorks/osdThread.c |
1484 | index 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() |
1695 | diff --git a/modules/libcom/src/osi/os/vxWorks/osdThread.h b/modules/libcom/src/osi/os/vxWorks/osdThread.h |
1696 | index 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 */ |
1720 | diff --git a/modules/libcom/test/epicsThreadTest.cpp b/modules/libcom/test/epicsThreadTest.cpp |
1721 | index 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 | } |
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.