Merge ~info-martin-konrad/epics-base:dont-nuke-global-cac-thread-id-in-exit-handler into ~epics-core/epics-base/+git/epics-base:3.15

Proposed by Martin Konrad
Status: Merged
Approved by: Andrew Johnson
Approved revision: 25576c316ac1212dc7b3703d5d5ab36a43608050
Merged at revision: b811d3402f24b1d081d0d227258309edc82d00ab
Proposed branch: ~info-martin-konrad/epics-base:dont-nuke-global-cac-thread-id-in-exit-handler
Merge into: ~epics-core/epics-base/+git/epics-base:3.15
Diff against target: 44 lines (+0/-9)
2 files modified
src/ca/client/ca_client_context.cpp (+0/-7)
src/ca/client/oldAccess.h (+0/-2)
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Ralph Lange Approve
mdavidsaver Approve
Review via email: mp+366996@code.launchpad.net
To post a comment you must log in.
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

epicsThreadPrivateDelete() has invalided the storage pointed to by caClientCallbackThreadId. Wouldn't this change be replacing a NULL dereference with a use after free()?

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

Oops, of course you're right. I guess taking out the epicsThreadPrivateDelete() call is not really an option either because then the object is leaked...

I guess the right approach would be to use a shared pointer to get it cleaned up when it's not used anymore. But that would obviously break the interface (not sure if we can avoid this here, though). Any thoughts?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Yes, ref. counting would probably be the best way to avoid an exit handler here. I think this could be done with an atomic inc. in ca_context_create() and dec. in ca_context_destroy().

And then I notice that caClientContextIdOnce in access.cpp is already being leaked... So maybe just remove "cacExitHandler()"? This would be far from the only one-time leak in Base.

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

RTEMS and VxWorks both use the same basic implementation of epicsThreadPrivate variables for which the delete function does nothing:

> void epicsThreadPrivateDelete (epicsThreadPrivateId id)
> {
> /* empty */
> }

I thought that we used this code everywhere, (I'd forgotten that we even provided a Delete() function for the API) but I was obviously wrong; both the Posix and Windows implementations /are/ different and rely on the OS to allocate IDs. On Posix reusing a pthread_key_t value after deleting it results in undefined behavior, but I suspect that in practice the storage in any other threads doesn't get deleted immediately, which is why Bruno's test worked.

The rationale section in the Linux pthread_key_delete() man-page describes how fraught with difficulty deleting the ID/key of a thread-private variable is in practice, and in Base most code doesn't delete the ID at all. Both the 3.15 and 7.0 branches contain 10 calls to epicsThreadPrivateCreate() and only 2 calls to epicsThreadPrivateDelete(), one of which is in the destructor for the C++ epicsThreadPrivate<T> template class (which only seems to be used in test programs), the other is here in CA's cacExitHandler().

I agree with Michael that removing the cacExitHandler() is probably the best solution. My previous commit already added a Release Note, the wording of which still applies, so it would make sense to modify this merge to finish off the necessary changes.

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

Thanks for your input! I amended my commit to eliminate cacExitHandler().

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I think that either the "caClientCallbackThreadId = 0;" needs to be restored, or cacExitHandler() completely removed.

Also, the epicsAtExit() callback ordering in pyDevSup has separately been addressed.

https://github.com/mdavidsaver/pyDevSup/pull/20

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

@MAD: Huh?!

@Martin: You missed the function declaration for cacExitHandler() that appears in one of the CA header files. Could you also rebase this branch against the latest 3.15 commit so this Merge doesn't show a conflict, which I suspect is what's confusing Michael.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Ah, yes. The MR diff is total wrong. Way to go LP. b905218cdccf1618c5c685063267564edc94f27f looks fine.

> You missed the function declaration for cacExitHandler() that appears

Also the 'friend' line in oldAccess.h. Looks fine once this is done.

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

I removed the declaration and the "friend" line and rebased.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Looks ok now.

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

Andrew, are there still changes you want?

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

Looks fine to me.

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

Clean out this old disapproval, I originally approved this proposal on 5/20.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/ca/client/ca_client_context.cpp b/src/ca/client/ca_client_context.cpp
2index 95cd6cb..ce8de1e 100644
3--- a/src/ca/client/ca_client_context.cpp
4+++ b/src/ca/client/ca_client_context.cpp
5@@ -45,19 +45,12 @@ static epicsThreadOnceId cacOnce = EPICS_THREAD_ONCE_INIT;
6
7 const unsigned ca_client_context :: flushBlockThreshold = 0x58000;
8
9-extern "C" void cacExitHandler ( void *)
10-{
11- epicsThreadPrivateDelete ( caClientCallbackThreadId );
12- delete ca_client_context::pDefaultServiceInstallMutex;
13-}
14-
15 // runs once only for each process
16 extern "C" void cacOnceFunc ( void * )
17 {
18 caClientCallbackThreadId = epicsThreadPrivateCreate ();
19 assert ( caClientCallbackThreadId );
20 ca_client_context::pDefaultServiceInstallMutex = newEpicsMutex;
21- epicsAtExit ( cacExitHandler,0 );
22 }
23
24 extern epicsThreadPrivateId caClientContextId;
25diff --git a/src/ca/client/oldAccess.h b/src/ca/client/oldAccess.h
26index 04f5518..daa4cc9 100644
27--- a/src/ca/client/oldAccess.h
28+++ b/src/ca/client/oldAccess.h
29@@ -288,7 +288,6 @@ private:
30 };
31
32 extern "C" void cacOnceFunc ( void * );
33-extern "C" void cacExitHandler ( void *);
34
35 struct ca_client_context : public cacContextNotify
36 {
37@@ -428,7 +427,6 @@ private:
38 ca_client_context & operator = ( const ca_client_context & );
39
40 friend void cacOnceFunc ( void * );
41- friend void cacExitHandler ( void *);
42 static cacService * pDefaultService;
43 static epicsMutex * pDefaultServiceInstallMutex;
44 static const unsigned flushBlockThreshold;

Subscribers

People subscribed via source and target branches