diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index 859ff7b29..b2465e53c 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -13,6 +13,12 @@ +

Fix DNS related crash on exit

+ +

The attempt to fix DNS related delays for short lived CLI programs (eg. caget) +in lp:1527636 introduced a bug which cased these short lived clients to crash on exit. +This bug should now be fixed.

+

Server bind issue on Windows

When a National Instruments network variables CA server is already running on diff --git a/src/libCom/misc/ipAddrToAsciiAsynchronous.cpp b/src/libCom/misc/ipAddrToAsciiAsynchronous.cpp index 6dbdc405e..c6fea15b4 100644 --- a/src/libCom/misc/ipAddrToAsciiAsynchronous.cpp +++ b/src/libCom/misc/ipAddrToAsciiAsynchronous.cpp @@ -18,6 +18,9 @@ #include #include +//#define EPICS_FREELIST_DEBUG +#define EPICS_PRIVATE_API + #define epicsExportSharedSymbols #include "ipAddrToAsciiAsynchronous.h" #include "epicsThread.h" @@ -45,7 +48,6 @@ public: < ipAddrToAsciiTransactionPrivate, 0x80 > & ); epicsPlacementDeleteOperator (( void *, tsFreeList < ipAddrToAsciiTransactionPrivate, 0x80 > & )) -private: osiSockAddr addr; ipAddrToAsciiEnginePrivate & engine; ipAddrToAsciiCallBack * pCB; @@ -54,7 +56,7 @@ private: void release (); void * operator new ( size_t ); void operator delete ( void * ); - friend class ipAddrToAsciiEnginePrivate; +private: ipAddrToAsciiTransactionPrivate & operator = ( const ipAddrToAsciiTransactionPrivate & ); ipAddrToAsciiTransactionPrivate ( const ipAddrToAsciiTransactionPrivate & ); }; @@ -75,41 +77,54 @@ extern "C" { static void ipAddrToAsciiEngineGlobalMutexConstruct ( void * ); } -// - this class executes the synchronous DNS query -// - it creates one thread -class ipAddrToAsciiEnginePrivate : - public ipAddrToAsciiEngine, - public epicsThreadRunable { -public: - ipAddrToAsciiEnginePrivate (); - virtual ~ipAddrToAsciiEnginePrivate (); - void show ( unsigned level ) const; -private: +namespace { +struct ipAddrToAsciiGlobal : public epicsThreadRunable { + ipAddrToAsciiGlobal(); + virtual ~ipAddrToAsciiGlobal() {} + + virtual void run (); + char nameTmp [1024]; - tsFreeList - < ipAddrToAsciiTransactionPrivate, 0x80 > + tsFreeList + < ipAddrToAsciiTransactionPrivate, 0x80 > transactionFreeList; tsDLList < ipAddrToAsciiTransactionPrivate > labor; mutable epicsMutex mutex; epicsEvent laborEvent; epicsEvent destructorBlockEvent; epicsThread thread; + // pCurrent may be changed by any thread (worker or other) ipAddrToAsciiTransactionPrivate * pCurrent; + // pActive may only be changed by the worker + ipAddrToAsciiTransactionPrivate * pActive; unsigned cancelPendingCount; bool exitFlag; bool callbackInProgress; - static ipAddrToAsciiEnginePrivate * pEngine; +}; +} + +// - this class executes the synchronous DNS query +// - it creates one thread +class ipAddrToAsciiEnginePrivate : + public ipAddrToAsciiEngine { +public: + ipAddrToAsciiEnginePrivate() :refcount(1u), released(false) {} + virtual ~ipAddrToAsciiEnginePrivate () {} + void show ( unsigned level ) const; + + unsigned refcount; + bool released; + + static ipAddrToAsciiGlobal * pEngine; ipAddrToAsciiTransaction & createTransaction (); - void release (); - void run (); - ipAddrToAsciiEnginePrivate ( const ipAddrToAsciiEngine & ); + void release (); + +private: + ipAddrToAsciiEnginePrivate ( const ipAddrToAsciiEngine & ); ipAddrToAsciiEnginePrivate & operator = ( const ipAddrToAsciiEngine & ); - friend class ipAddrToAsciiEngine; - friend class ipAddrToAsciiTransactionPrivate; - friend void ipAddrToAsciiEngineGlobalMutexConstruct ( void * ); }; -ipAddrToAsciiEnginePrivate * ipAddrToAsciiEnginePrivate :: pEngine = 0; +ipAddrToAsciiGlobal * ipAddrToAsciiEnginePrivate :: pEngine = 0; static epicsThreadOnceId ipAddrToAsciiEngineGlobalMutexOnceFlag = EPICS_THREAD_ONCE_INIT; // the users are not required to supply a show routine @@ -124,12 +139,24 @@ ipAddrToAsciiEngine::~ipAddrToAsciiEngine () {} static void ipAddrToAsciiEngineGlobalMutexConstruct ( void * ) { try{ - ipAddrToAsciiEnginePrivate::pEngine = new ipAddrToAsciiEnginePrivate (); + ipAddrToAsciiEnginePrivate::pEngine = new ipAddrToAsciiGlobal (); }catch(std::exception& e){ errlogPrintf("ipAddrToAsciiEnginePrivate ctor fails with: %s\n", e.what()); } } +void ipAddrToAsciiEngine::cleanup() +{ + { + epicsGuard G(ipAddrToAsciiEnginePrivate::pEngine->mutex); + ipAddrToAsciiEnginePrivate::pEngine->exitFlag = true; + } + ipAddrToAsciiEnginePrivate::pEngine->laborEvent.signal(); + ipAddrToAsciiEnginePrivate::pEngine->thread.exitWait(); + delete ipAddrToAsciiEnginePrivate::pEngine; + ipAddrToAsciiEnginePrivate::pEngine = 0; +} + // for now its probably sufficent to allocate one // DNS transaction thread for all codes sharing // the same process that need DNS services but we @@ -141,41 +168,78 @@ ipAddrToAsciiEngine & ipAddrToAsciiEngine::allocate () ipAddrToAsciiEngineGlobalMutexConstruct, 0 ); if(!ipAddrToAsciiEnginePrivate::pEngine) throw std::runtime_error("ipAddrToAsciiEngine::allocate fails"); - return * ipAddrToAsciiEnginePrivate::pEngine; + return * new ipAddrToAsciiEnginePrivate(); } -ipAddrToAsciiEnginePrivate::ipAddrToAsciiEnginePrivate () : +ipAddrToAsciiGlobal::ipAddrToAsciiGlobal () : thread ( *this, "ipToAsciiProxy", epicsThreadGetStackSize(epicsThreadStackBig), epicsThreadPriorityLow ), - pCurrent ( 0 ), cancelPendingCount ( 0u ), exitFlag ( false ), + pCurrent ( 0 ), pActive ( 0 ), cancelPendingCount ( 0u ), exitFlag ( false ), callbackInProgress ( false ) { this->thread.start (); // start the thread } -ipAddrToAsciiEnginePrivate::~ipAddrToAsciiEnginePrivate () -{ - { - epicsGuard < epicsMutex > guard ( this->mutex ); - this->exitFlag = true; - } - this->laborEvent.signal (); - this->thread.exitWait (); -} void ipAddrToAsciiEnginePrivate::release () { + bool last; + { + epicsGuard < epicsMutex > guard ( this->pEngine->mutex ); + if(released) + throw std::logic_error("Engine release() called again!"); + + // released==true prevents new transactions + released = true; + + { + // cancel any pending transactions + tsDLIter < ipAddrToAsciiTransactionPrivate > it(pEngine->labor.firstIter()); + while(it.valid()) { + ipAddrToAsciiTransactionPrivate *trn = it.pointer(); + ++it; + + if(this==&trn->engine) { + trn->pending = false; + pEngine->labor.remove(*trn); + } + } + + // cancel transaction in lookup or callback + if (pEngine->pCurrent && this==&pEngine->pCurrent->engine) { + pEngine->pCurrent->pending = false; + pEngine->pCurrent = 0; + } + + // wait for completion of in-progress callback + pEngine->cancelPendingCount++; + while(pEngine->pActive && this==&pEngine->pActive->engine + && ! pEngine->thread.isCurrentThread()) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + pEngine->destructorBlockEvent.wait(); + } + pEngine->cancelPendingCount--; + if(pEngine->cancelPendingCount) + pEngine->destructorBlockEvent.signal(); + } + + assert(refcount>0); + last = 0==--refcount; + } + if(last) { + delete this; + } } void ipAddrToAsciiEnginePrivate::show ( unsigned level ) const { - epicsGuard < epicsMutex > guard ( this->mutex ); + epicsGuard < epicsMutex > guard ( this->pEngine->mutex ); printf ( "ipAddrToAsciiEngine at %p with %u requests pending\n", - static_cast (this), this->labor.count () ); + static_cast (this), this->pEngine->labor.count () ); if ( level > 0u ) { - tsDLIterConst < ipAddrToAsciiTransactionPrivate > - pItem = this->labor.firstIter (); + tsDLIter < ipAddrToAsciiTransactionPrivate > + pItem = this->pEngine->labor.firstIter (); while ( pItem.valid () ) { pItem->show ( level - 1u ); pItem++; @@ -183,10 +247,10 @@ void ipAddrToAsciiEnginePrivate::show ( unsigned level ) const } if ( level > 1u ) { printf ( "mutex:\n" ); - this->mutex.show ( level - 2u ); + this->pEngine->mutex.show ( level - 2u ); printf ( "laborEvent:\n" ); - this->laborEvent.show ( level - 2u ); - printf ( "exitFlag boolean = %u\n", this->exitFlag ); + this->pEngine->laborEvent.show ( level - 2u ); + printf ( "exitFlag boolean = %u\n", this->pEngine->exitFlag ); printf ( "exit event:\n" ); } } @@ -226,10 +290,20 @@ void ipAddrToAsciiTransactionPrivate::operator delete ( void * ) ipAddrToAsciiTransaction & ipAddrToAsciiEnginePrivate::createTransaction () { - return * new ( this->transactionFreeList ) ipAddrToAsciiTransactionPrivate ( *this ); + epicsGuard G(this->pEngine->mutex); + if(this->released) + throw std::logic_error("createTransaction() on release()'d ipAddrToAsciiEngine"); + + assert(this->refcount>0); + + ipAddrToAsciiTransactionPrivate *ret = new ( this->pEngine->transactionFreeList ) ipAddrToAsciiTransactionPrivate ( *this ); + + this->refcount++; + + return * ret; } -void ipAddrToAsciiEnginePrivate::run () +void ipAddrToAsciiGlobal::run () { epicsGuard < epicsMutex > guard ( this->mutex ); while ( ! this->exitFlag ) { @@ -267,7 +341,7 @@ void ipAddrToAsciiEnginePrivate::run () // fix for lp:1580623 // a destructing cac sets pCurrent to NULL, so // make local copy to avoid race when releasing the guard - ipAddrToAsciiTransactionPrivate *pCur = this->pCurrent; + ipAddrToAsciiTransactionPrivate *pCur = pActive = pCurrent; this->callbackInProgress = true; { @@ -277,6 +351,7 @@ void ipAddrToAsciiEnginePrivate::run () } this->callbackInProgress = false; + pActive = 0; if ( this->pCurrent ) { this->pCurrent->pending = false; @@ -300,44 +375,53 @@ ipAddrToAsciiTransactionPrivate::ipAddrToAsciiTransactionPrivate void ipAddrToAsciiTransactionPrivate::release () { this->~ipAddrToAsciiTransactionPrivate (); - this->engine.transactionFreeList.release ( this ); + this->engine.pEngine->transactionFreeList.release ( this ); } ipAddrToAsciiTransactionPrivate::~ipAddrToAsciiTransactionPrivate () { - epicsGuard < epicsMutex > guard ( this->engine.mutex ); - while ( this->pending ) { - if ( this->engine.pCurrent == this && - this->engine.callbackInProgress && - ! this->engine.thread.isCurrentThread() ) { - // cancel from another thread while callback in progress - // waits for callback to complete - assert ( this->engine.cancelPendingCount < UINT_MAX ); - this->engine.cancelPendingCount++; - { - epicsGuardRelease < epicsMutex > unguard ( guard ); - this->engine.destructorBlockEvent.wait (); - } - assert ( this->engine.cancelPendingCount > 0u ); - this->engine.cancelPendingCount--; - if ( ! this->pending ) { - if ( this->engine.cancelPendingCount ) { - this->engine.destructorBlockEvent.signal (); + ipAddrToAsciiGlobal *pGlobal = this->engine.pEngine; + bool last; + { + epicsGuard < epicsMutex > guard ( pGlobal->mutex ); + while ( this->pending ) { + if ( pGlobal->pCurrent == this && + pGlobal->callbackInProgress && + ! pGlobal->thread.isCurrentThread() ) { + // cancel from another thread while callback in progress + // waits for callback to complete + assert ( pGlobal->cancelPendingCount < UINT_MAX ); + pGlobal->cancelPendingCount++; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + pGlobal->destructorBlockEvent.wait (); + } + assert ( pGlobal->cancelPendingCount > 0u ); + pGlobal->cancelPendingCount--; + if ( ! this->pending ) { + if ( pGlobal->cancelPendingCount ) { + pGlobal->destructorBlockEvent.signal (); + } + break; } - break; - } - } - else { - if ( this->engine.pCurrent == this ) { - // cancel from callback, or while lookup in progress - this->engine.pCurrent = 0; } else { - // cancel before lookup starts - this->engine.labor.remove ( *this ); + if ( pGlobal->pCurrent == this ) { + // cancel from callback, or while lookup in progress + pGlobal->pCurrent = 0; + } + else { + // cancel before lookup starts + pGlobal->labor.remove ( *this ); + } + this->pending = false; } - this->pending = false; } + assert(this->engine.refcount>0); + last = 0==--this->engine.refcount; + } + if(last) { + delete &this->engine; } } @@ -345,15 +429,21 @@ void ipAddrToAsciiTransactionPrivate::ipAddrToAscii ( const osiSockAddr & addrIn, ipAddrToAsciiCallBack & cbIn ) { bool success; + ipAddrToAsciiGlobal *pGlobal = this->engine.pEngine; { - epicsGuard < epicsMutex > guard ( this->engine.mutex ); - // put some reasonable limit on queue expansion - if ( !this->pending && engine.labor.count () < 16u ) { + epicsGuard < epicsMutex > guard ( pGlobal->mutex ); + + if (this->engine.released) { + errlogPrintf("Warning: ipAddrToAscii on transaction with release()'d ipAddrToAsciiEngine"); + success = false; + + } else if ( !this->pending && pGlobal->labor.count () < 16u ) { + // put some reasonable limit on queue expansion this->addr = addrIn; this->pCB = & cbIn; this->pending = true; - this->engine.labor.add ( *this ); + pGlobal->labor.add ( *this ); success = true; } else { @@ -362,7 +452,7 @@ void ipAddrToAsciiTransactionPrivate::ipAddrToAscii ( } if ( success ) { - this->engine.laborEvent.signal (); + pGlobal->laborEvent.signal (); } else { char autoNameTmp[256]; @@ -379,7 +469,7 @@ osiSockAddr ipAddrToAsciiTransactionPrivate::address () const void ipAddrToAsciiTransactionPrivate::show ( unsigned level ) const { - epicsGuard < epicsMutex > guard ( this->engine.mutex ); + epicsGuard < epicsMutex > guard ( this->engine.pEngine->mutex ); char ipAddr [64]; sockAddrToDottedIP ( &this->addr.sa, ipAddr, sizeof ( ipAddr ) ); printf ( "ipAddrToAsciiTransactionPrivate for address %s\n", ipAddr ); diff --git a/src/libCom/misc/ipAddrToAsciiAsynchronous.h b/src/libCom/misc/ipAddrToAsciiAsynchronous.h index f2de879b6..5da06860a 100644 --- a/src/libCom/misc/ipAddrToAsciiAsynchronous.h +++ b/src/libCom/misc/ipAddrToAsciiAsynchronous.h @@ -44,6 +44,10 @@ public: static ipAddrToAsciiEngine & allocate (); protected: virtual ~ipAddrToAsciiEngine () = 0; +public: +#ifdef EPICS_PRIVATE_API + static void cleanup(); +#endif }; #endif // ifdef ipAddrToAsciiAsynchronous_h diff --git a/src/libCom/test/Makefile b/src/libCom/test/Makefile index 5f486938f..6239e0b41 100644 --- a/src/libCom/test/Makefile +++ b/src/libCom/test/Makefile @@ -11,6 +11,7 @@ TOP=../../.. include $(TOP)/configure/CONFIG PROD_LIBS += Com +PROD_SYS_LIBS_WIN32 += ws2_32 advapi32 user32 TESTPROD_HOST += epicsUnitTestTest epicsUnitTestTest_SRCS += epicsUnitTestTest.c @@ -151,6 +152,10 @@ epicsMessageQueueTest_SRCS += epicsMessageQueueTest.cpp testHarness_SRCS += epicsMessageQueueTest.cpp TESTS += epicsMessageQueueTest +TESTPROD_HOST += ipAddrToAsciiTest +ipAddrToAsciiTest_SRCS += ipAddrToAsciiTest.cpp +testHarness_SRCS += ipAddrToAsciiTest.cpp +TESTS += ipAddrToAsciiTest # The testHarness runs all the test programs in a known working order. testHarness_SRCS += epicsRunLibComTests.c diff --git a/src/libCom/test/epicsRunLibComTests.c b/src/libCom/test/epicsRunLibComTests.c index 81290644a..16d316592 100644 --- a/src/libCom/test/epicsRunLibComTests.c +++ b/src/libCom/test/epicsRunLibComTests.c @@ -41,6 +41,7 @@ int macEnvExpandTest(void); int ringPointerTest(void); int ringBytesTest(void); int blockingSockTest(void); +int ipAddrToAsciiTest(void); int epicsSockResolveTest(void); int taskwdTest(void); int epicsExitTest(void); @@ -102,6 +103,8 @@ void epicsRunLibComTests(void) runTest(ringBytesTest); runTest(blockingSockTest); + + runTest(ipAddrToAsciiTest); runTest(epicsSockResolveTest); diff --git a/src/libCom/test/ipAddrToAsciiTest.cpp b/src/libCom/test/ipAddrToAsciiTest.cpp new file mode 100644 index 000000000..f9323374d --- /dev/null +++ b/src/libCom/test/ipAddrToAsciiTest.cpp @@ -0,0 +1,165 @@ +/*************************************************************************\ +* Copyright (c) 2017 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#define EPICS_PRIVATE_API + +#include "epicsMutex.h" +#include "epicsGuard.h" +#include "epicsThread.h" +#include "epicsEvent.h" +#include "ipAddrToAsciiAsynchronous.h" + +#include "epicsUnitTest.h" +#include "testMain.h" + +namespace { + +typedef epicsGuard Guard; +typedef epicsGuardRelease UnGuard; + +struct CB : public ipAddrToAsciiCallBack +{ + const char *name; + epicsMutex mutex; + epicsEvent starter, blocker, complete; + bool started, cont, done; + CB(const char *name) : name(name), started(false), cont(false), done(false) {} + virtual ~CB() {} + virtual void transactionComplete ( const char * pHostName ) + { + Guard G(mutex); + started = true; + starter.signal(); + testDiag("In transactionComplete(%s) for %s", pHostName, name); + while(!cont) { + UnGuard U(G); + if(!blocker.wait(2.0)) + break; + } + done = true; + complete.signal(); + } + void waitStart() + { + Guard G(mutex); + while(!started) { + UnGuard U(G); + if(!starter.wait(2.0)) + break; + } + } + void poke() + { + testDiag("Poke"); + Guard G(mutex); + cont = true; + blocker.signal(); + } + void finish() + { + testDiag("Finish"); + Guard G(mutex); + while(!done) { + UnGuard U(G); + if(!complete.wait(2.0)) + break; + } + testDiag("Finished"); + } +}; + +// ensure that lookup of 127.0.0.1 works +void doLookup(ipAddrToAsciiEngine& engine) +{ + testDiag("In doLookup"); + + ipAddrToAsciiTransaction& trn(engine.createTransaction()); + CB cb("cb"); + osiSockAddr addr; + addr.ia.sin_family = AF_INET; + addr.ia.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.ia.sin_port = htons(42); + + testDiag("Start lookup"); + trn.ipAddrToAscii(addr, cb); + cb.poke(); + cb.finish(); + testOk1(cb.cont); + testOk1(cb.done); + + trn.release(); +} + +// Test cancel of pending transaction +void doCancel() +{ + testDiag("In doCancel"); + + ipAddrToAsciiEngine& engine1(ipAddrToAsciiEngine::allocate()); + ipAddrToAsciiEngine& engine2(ipAddrToAsciiEngine::allocate()); + + ipAddrToAsciiTransaction& trn1(engine1.createTransaction()), + & trn2(engine2.createTransaction()); + testOk1(&trn1!=&trn2); + CB cb1("cb1"), cb2("cb2"); + + osiSockAddr addr; + addr.ia.sin_family = AF_INET; + addr.ia.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.ia.sin_port = htons(42); + + // ensure that the worker thread is blocked with a transaction from engine1 + testDiag("Start lookup1"); + trn1.ipAddrToAscii(addr, cb1); + testDiag("Wait start1"); + cb1.waitStart(); + + testDiag("Start lookup2"); + trn2.ipAddrToAscii(addr, cb2); + + testDiag("release engine2, implicitly cancels lookup2"); + engine2.release(); + + cb2.poke(); + testDiag("Wait for lookup2 timeout"); + cb2.finish(); + testOk1(!cb2.done); + + testDiag("Complete lookup1"); + cb1.poke(); + cb1.finish(); + testOk1(cb1.done); + + engine1.release(); + + trn1.release(); + trn2.release(); +} + +} // namespace + +MAIN(ipAddrToAsciiTest) +{ + testPlan(5); + { + ipAddrToAsciiEngine& engine(ipAddrToAsciiEngine::allocate()); + doLookup(engine); + engine.release(); + } + doCancel(); + // TODO: somehow test cancel of in-progress callback + // allow time for any un-canceled transcations to crash us... + epicsThreadSleep(1.0); + +#ifdef __linux__ + ipAddrToAsciiEngine::cleanup(); +#endif + + return testDone(); +}