Merge lp:~epics-core/epics-base/fix-async-dns into lp:~epics-core/epics-base/3.14

Proposed by mdavidsaver
Status: Merged
Merge reported by: Andrew Johnson
Merged at revision: not available
Proposed branch: lp:~epics-core/epics-base/fix-async-dns
Merge into: lp:~epics-core/epics-base/3.14
Diff against target: 603 lines (+339/-81)
4 files modified
src/libCom/misc/ipAddrToAsciiAsynchronous.cpp (+171/-81)
src/libCom/misc/ipAddrToAsciiAsynchronous.h (+4/-0)
src/libCom/test/Makefile (+3/-0)
src/libCom/test/ipAddrToAsciiTest.cpp (+161/-0)
To merge this branch: bzr merge lp:~epics-core/epics-base/fix-async-dns
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Ralph Lange Approve
Review via email: mp+318385@code.launchpad.net

Description of the change

An attempt to properly address the regressions exposed/uncovered by lp:1527636. Previous to that change the ipAddrToAscii worker thread was stopped by the last call to ipAddrToAsciiEngine::release(), preventing further transactions from executing. The change (by myself) for lp:1527636 made release() a no-op, which did not stop pending transactions. This lead to races involving msgForMultiply, and perhaps others.

The original code was still open to races when more than one call to ipAddrToAsciiEngine::allocate() was made. The shared singleton ipAddrToAsciiEngine would not stop the worker until the last release(). So the race with msgForMultiply could happen if, for example, more than one CA context exists.

This branch attempt to achieve the best of both worlds. The worker thread is not stopped, but the semantics of release() are restored, and improved. The semantics of the API should be otherwise unchanged.

Instead of being a singleton ipAddrToAsciiEngine becomes an empty struct to trace references. The singleton is renamed ipAddrToAsciiGlobal and is fully hidden from user code.

Each all to ipAddrToAsciiEngine::allocate() returns a new, unique, ipAddrToAsciiEngine instance. release() must be called to "destroy". ipAddrToAsciiEnginePrivate carries a reference counter to ensure it outlives any transactions which reference it.

Calling ipAddrToAsciiEngine::release() marks the engine as "released" which will any transactions from being pended. It cancels and already pending transactions with engine==this and wait()s if the currently executing callback is the same. Canceled transactions are not executed.

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

Subject to merge testing, accepted in principle at 3/14/2017 F2F meeting.

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

Code looks good to me.

I am not able to create an original setup that allows reproducing the error.
However, running the added test against an unpatched base I see occasional failures (test 4 fails in about 20% of the cases), while running it against this branch does not fail.

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

Is there any reason to not run the new ipAddrToAsciiTest code on RTEMS and/or VxWorks? It has not been added to epicsRunLibComTests.c so it isn't included in the test harness at the moment.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

No reason, an oversight on my part. I'll add this evening if you haven't already.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Done w/ the git branch. I'm going to try to see if I can switch this MR to the git branches w/o restarting it. Be prepared for some email...

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

FYI, "Resubmit proposal" errors when trying to switch to git branches. I'm not going to manually start a new MR unless a larger change is needed.

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

This passes its self-test fine on VxWorks.

However a second run inside the harness (without rebooting first) caused this:

***** ipAddrToAsciiTest *****
1..5
0x475798 (tShell0): Unhandled C++ exception resulted in call to terminate

0x00548424 epicsRunLibComTests+0x250: runTestFunc ()
0x00559584 runTestFunc +0x4c : ipAddrToAsciiTest ()
0x00547500 ipAddrToAsciiTest+0x7c : 0x00557b90 ()
0x00557b90 ipAddrToAsciiEngine::allocate()+0x47c: __cxa_throw ()
0x0027c578 __cxa_throw +0x70 : std::terminate() ()
0x00279e64 __cxxabiv1::__unexpected(void (*)())+0x0 : __cxxabiv1::__terminate(void (*)()) ()
0x00279e30 __cxxabiv1::__terminate(void (*)())+0x18 : cplusTerminate ()
0x00133c70 cplusTerminate+0x4c : taskSuspend ()

Is the ipAddrToAsciiEngine::cleanup() routine necessary (this test is the only place it's called at the moment)? Could this become an epicsAtExit() routine, or would that just bring back the shutdown delays that caused these changes in the first place? Cleaning up things which only get initialized in an epicsThreadOnce() routine makes them impossible to use afterwards, so you couldn't call ipAddrToAsciiEngine::cleanup() in dbUnitTest.c; I'm not really sure where it could be used.

I'm not really expecting to be able to run the self-tests more than once without rebooting, but it is nice if they don't crash since many of the tests /can/ be run more than once.

There doesn't appear to be any documentation for this subsystem that could be updated, but the previous fix was mentioned in the 3.14.12.6 Release Notes so it would be good to have this fix covered there too.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> Is the ipAddrToAsciiEngine::cleanup() routine necessary ...

This exists to help valgrind to tell me if I'm doing something stupid. It can be skipped with no ill effects. Maybe #ifdef to omit on rtems/vxworks?

> Release Notes

ok

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

@anj done

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

Thanks. This branch is intended for merging into 3.14, which doesn't have valgrind/valgrind.h (nor does the 3.15 branch), so my build died:

../ipAddrToAsciiTest.cpp:10:31: fatal error: valgrind/valgrind.h: No such file or directory
 #include <valgrind/valgrind.h>
                               ^
compilation terminated.

However the failure didn't happen until it got to my windows-x64-mingw cross-compile; I got warnings from the VxWorks and RTEMS builds about the missing file, but they still succeeded because it isn't actually necessary to pull in that include file at all. The cleanup() routine is only useful for testing under valgrind, which only runs on Linux, so I just changed the conditional around the call to #ifdef __linux__ and now everything works.

Merging.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/libCom/misc/ipAddrToAsciiAsynchronous.cpp'
2--- src/libCom/misc/ipAddrToAsciiAsynchronous.cpp 2016-09-02 20:12:50 +0000
3+++ src/libCom/misc/ipAddrToAsciiAsynchronous.cpp 2017-02-27 16:54:43 +0000
4@@ -18,6 +18,9 @@
5 #include <stdexcept>
6 #include <stdio.h>
7
8+//#define EPICS_FREELIST_DEBUG
9+#define EPICS_PRIVATE_API
10+
11 #define epicsExportSharedSymbols
12 #include "ipAddrToAsciiAsynchronous.h"
13 #include "epicsThread.h"
14@@ -45,7 +48,6 @@
15 < ipAddrToAsciiTransactionPrivate, 0x80 > & );
16 epicsPlacementDeleteOperator (( void *, tsFreeList
17 < ipAddrToAsciiTransactionPrivate, 0x80 > & ))
18-private:
19 osiSockAddr addr;
20 ipAddrToAsciiEnginePrivate & engine;
21 ipAddrToAsciiCallBack * pCB;
22@@ -54,7 +56,7 @@
23 void release ();
24 void * operator new ( size_t );
25 void operator delete ( void * );
26- friend class ipAddrToAsciiEnginePrivate;
27+private:
28 ipAddrToAsciiTransactionPrivate & operator = ( const ipAddrToAsciiTransactionPrivate & );
29 ipAddrToAsciiTransactionPrivate ( const ipAddrToAsciiTransactionPrivate & );
30 };
31@@ -75,41 +77,54 @@
32 static void ipAddrToAsciiEngineGlobalMutexConstruct ( void * );
33 }
34
35-// - this class executes the synchronous DNS query
36-// - it creates one thread
37-class ipAddrToAsciiEnginePrivate :
38- public ipAddrToAsciiEngine,
39- public epicsThreadRunable {
40-public:
41- ipAddrToAsciiEnginePrivate ();
42- virtual ~ipAddrToAsciiEnginePrivate ();
43- void show ( unsigned level ) const;
44-private:
45+namespace {
46+struct ipAddrToAsciiGlobal : public epicsThreadRunable {
47+ ipAddrToAsciiGlobal();
48+ virtual ~ipAddrToAsciiGlobal() {}
49+
50+ virtual void run ();
51+
52 char nameTmp [1024];
53- tsFreeList
54- < ipAddrToAsciiTransactionPrivate, 0x80 >
55+ tsFreeList
56+ < ipAddrToAsciiTransactionPrivate, 0x80 >
57 transactionFreeList;
58 tsDLList < ipAddrToAsciiTransactionPrivate > labor;
59 mutable epicsMutex mutex;
60 epicsEvent laborEvent;
61 epicsEvent destructorBlockEvent;
62 epicsThread thread;
63+ // pCurrent may be changed by any thread (worker or other)
64 ipAddrToAsciiTransactionPrivate * pCurrent;
65+ // pActive may only be changed by the worker
66+ ipAddrToAsciiTransactionPrivate * pActive;
67 unsigned cancelPendingCount;
68 bool exitFlag;
69 bool callbackInProgress;
70- static ipAddrToAsciiEnginePrivate * pEngine;
71+};
72+}
73+
74+// - this class executes the synchronous DNS query
75+// - it creates one thread
76+class ipAddrToAsciiEnginePrivate :
77+ public ipAddrToAsciiEngine {
78+public:
79+ ipAddrToAsciiEnginePrivate() :refcount(1u), released(false) {}
80+ virtual ~ipAddrToAsciiEnginePrivate () {}
81+ void show ( unsigned level ) const;
82+
83+ unsigned refcount;
84+ bool released;
85+
86+ static ipAddrToAsciiGlobal * pEngine;
87 ipAddrToAsciiTransaction & createTransaction ();
88- void release ();
89- void run ();
90- ipAddrToAsciiEnginePrivate ( const ipAddrToAsciiEngine & );
91+ void release ();
92+
93+private:
94+ ipAddrToAsciiEnginePrivate ( const ipAddrToAsciiEngine & );
95 ipAddrToAsciiEnginePrivate & operator = ( const ipAddrToAsciiEngine & );
96- friend class ipAddrToAsciiEngine;
97- friend class ipAddrToAsciiTransactionPrivate;
98- friend void ipAddrToAsciiEngineGlobalMutexConstruct ( void * );
99 };
100
101-ipAddrToAsciiEnginePrivate * ipAddrToAsciiEnginePrivate :: pEngine = 0;
102+ipAddrToAsciiGlobal * ipAddrToAsciiEnginePrivate :: pEngine = 0;
103 static epicsThreadOnceId ipAddrToAsciiEngineGlobalMutexOnceFlag = EPICS_THREAD_ONCE_INIT;
104
105 // the users are not required to supply a show routine
106@@ -124,12 +139,24 @@
107 static void ipAddrToAsciiEngineGlobalMutexConstruct ( void * )
108 {
109 try{
110- ipAddrToAsciiEnginePrivate::pEngine = new ipAddrToAsciiEnginePrivate ();
111+ ipAddrToAsciiEnginePrivate::pEngine = new ipAddrToAsciiGlobal ();
112 }catch(std::exception& e){
113 errlogPrintf("ipAddrToAsciiEnginePrivate ctor fails with: %s\n", e.what());
114 }
115 }
116
117+void ipAddrToAsciiEngine::cleanup()
118+{
119+ {
120+ epicsGuard<epicsMutex> G(ipAddrToAsciiEnginePrivate::pEngine->mutex);
121+ ipAddrToAsciiEnginePrivate::pEngine->exitFlag = true;
122+ }
123+ ipAddrToAsciiEnginePrivate::pEngine->laborEvent.signal();
124+ ipAddrToAsciiEnginePrivate::pEngine->thread.exitWait();
125+ delete ipAddrToAsciiEnginePrivate::pEngine;
126+ ipAddrToAsciiEnginePrivate::pEngine = 0;
127+}
128+
129 // for now its probably sufficent to allocate one
130 // DNS transaction thread for all codes sharing
131 // the same process that need DNS services but we
132@@ -141,41 +168,78 @@
133 ipAddrToAsciiEngineGlobalMutexConstruct, 0 );
134 if(!ipAddrToAsciiEnginePrivate::pEngine)
135 throw std::runtime_error("ipAddrToAsciiEngine::allocate fails");
136- return * ipAddrToAsciiEnginePrivate::pEngine;
137+ return * new ipAddrToAsciiEnginePrivate();
138 }
139
140-ipAddrToAsciiEnginePrivate::ipAddrToAsciiEnginePrivate () :
141+ipAddrToAsciiGlobal::ipAddrToAsciiGlobal () :
142 thread ( *this, "ipToAsciiProxy",
143 epicsThreadGetStackSize(epicsThreadStackBig),
144 epicsThreadPriorityLow ),
145- pCurrent ( 0 ), cancelPendingCount ( 0u ), exitFlag ( false ),
146+ pCurrent ( 0 ), pActive ( 0 ), cancelPendingCount ( 0u ), exitFlag ( false ),
147 callbackInProgress ( false )
148 {
149 this->thread.start (); // start the thread
150 }
151
152-ipAddrToAsciiEnginePrivate::~ipAddrToAsciiEnginePrivate ()
153-{
154- {
155- epicsGuard < epicsMutex > guard ( this->mutex );
156- this->exitFlag = true;
157- }
158- this->laborEvent.signal ();
159- this->thread.exitWait ();
160-}
161
162 void ipAddrToAsciiEnginePrivate::release ()
163 {
164+ bool last;
165+ {
166+ epicsGuard < epicsMutex > guard ( this->pEngine->mutex );
167+ if(released)
168+ throw std::logic_error("Engine release() called again!");
169+
170+ // released==true prevents new transactions
171+ released = true;
172+
173+ {
174+ // cancel any pending transactions
175+ tsDLIter < ipAddrToAsciiTransactionPrivate > it(pEngine->labor.firstIter());
176+ while(it.valid()) {
177+ ipAddrToAsciiTransactionPrivate *trn = it.pointer();
178+ ++it;
179+
180+ if(this==&trn->engine) {
181+ trn->pending = false;
182+ pEngine->labor.remove(*trn);
183+ }
184+ }
185+
186+ // cancel transaction in lookup or callback
187+ if (pEngine->pCurrent && this==&pEngine->pCurrent->engine) {
188+ pEngine->pCurrent->pending = false;
189+ pEngine->pCurrent = 0;
190+ }
191+
192+ // wait for completion of in-progress callback
193+ pEngine->cancelPendingCount++;
194+ while(pEngine->pActive && this==&pEngine->pActive->engine
195+ && ! pEngine->thread.isCurrentThread()) {
196+ epicsGuardRelease < epicsMutex > unguard ( guard );
197+ pEngine->destructorBlockEvent.wait();
198+ }
199+ pEngine->cancelPendingCount--;
200+ if(pEngine->cancelPendingCount)
201+ pEngine->destructorBlockEvent.signal();
202+ }
203+
204+ assert(refcount>0);
205+ last = 0==--refcount;
206+ }
207+ if(last) {
208+ delete this;
209+ }
210 }
211
212 void ipAddrToAsciiEnginePrivate::show ( unsigned level ) const
213 {
214- epicsGuard < epicsMutex > guard ( this->mutex );
215+ epicsGuard < epicsMutex > guard ( this->pEngine->mutex );
216 printf ( "ipAddrToAsciiEngine at %p with %u requests pending\n",
217- static_cast <const void *> (this), this->labor.count () );
218+ static_cast <const void *> (this), this->pEngine->labor.count () );
219 if ( level > 0u ) {
220- tsDLIterConst < ipAddrToAsciiTransactionPrivate >
221- pItem = this->labor.firstIter ();
222+ tsDLIter < ipAddrToAsciiTransactionPrivate >
223+ pItem = this->pEngine->labor.firstIter ();
224 while ( pItem.valid () ) {
225 pItem->show ( level - 1u );
226 pItem++;
227@@ -183,10 +247,10 @@
228 }
229 if ( level > 1u ) {
230 printf ( "mutex:\n" );
231- this->mutex.show ( level - 2u );
232+ this->pEngine->mutex.show ( level - 2u );
233 printf ( "laborEvent:\n" );
234- this->laborEvent.show ( level - 2u );
235- printf ( "exitFlag boolean = %u\n", this->exitFlag );
236+ this->pEngine->laborEvent.show ( level - 2u );
237+ printf ( "exitFlag boolean = %u\n", this->pEngine->exitFlag );
238 printf ( "exit event:\n" );
239 }
240 }
241@@ -226,10 +290,20 @@
242
243 ipAddrToAsciiTransaction & ipAddrToAsciiEnginePrivate::createTransaction ()
244 {
245- return * new ( this->transactionFreeList ) ipAddrToAsciiTransactionPrivate ( *this );
246+ epicsGuard <epicsMutex> G(this->pEngine->mutex);
247+ if(this->released)
248+ throw std::logic_error("createTransaction() on release()'d ipAddrToAsciiEngine");
249+
250+ assert(this->refcount>0);
251+
252+ ipAddrToAsciiTransactionPrivate *ret = new ( this->pEngine->transactionFreeList ) ipAddrToAsciiTransactionPrivate ( *this );
253+
254+ this->refcount++;
255+
256+ return * ret;
257 }
258
259-void ipAddrToAsciiEnginePrivate::run ()
260+void ipAddrToAsciiGlobal::run ()
261 {
262 epicsGuard < epicsMutex > guard ( this->mutex );
263 while ( ! this->exitFlag ) {
264@@ -267,7 +341,7 @@
265 // fix for lp:1580623
266 // a destructing cac sets pCurrent to NULL, so
267 // make local copy to avoid race when releasing the guard
268- ipAddrToAsciiTransactionPrivate *pCur = this->pCurrent;
269+ ipAddrToAsciiTransactionPrivate *pCur = pActive = pCurrent;
270 this->callbackInProgress = true;
271
272 {
273@@ -277,6 +351,7 @@
274 }
275
276 this->callbackInProgress = false;
277+ pActive = 0;
278
279 if ( this->pCurrent ) {
280 this->pCurrent->pending = false;
281@@ -300,44 +375,53 @@
282 void ipAddrToAsciiTransactionPrivate::release ()
283 {
284 this->~ipAddrToAsciiTransactionPrivate ();
285- this->engine.transactionFreeList.release ( this );
286+ this->engine.pEngine->transactionFreeList.release ( this );
287 }
288
289 ipAddrToAsciiTransactionPrivate::~ipAddrToAsciiTransactionPrivate ()
290 {
291- epicsGuard < epicsMutex > guard ( this->engine.mutex );
292- while ( this->pending ) {
293- if ( this->engine.pCurrent == this &&
294- this->engine.callbackInProgress &&
295- ! this->engine.thread.isCurrentThread() ) {
296- // cancel from another thread while callback in progress
297- // waits for callback to complete
298- assert ( this->engine.cancelPendingCount < UINT_MAX );
299- this->engine.cancelPendingCount++;
300- {
301- epicsGuardRelease < epicsMutex > unguard ( guard );
302- this->engine.destructorBlockEvent.wait ();
303- }
304- assert ( this->engine.cancelPendingCount > 0u );
305- this->engine.cancelPendingCount--;
306- if ( ! this->pending ) {
307- if ( this->engine.cancelPendingCount ) {
308- this->engine.destructorBlockEvent.signal ();
309- }
310- break;
311- }
312- }
313- else {
314- if ( this->engine.pCurrent == this ) {
315- // cancel from callback, or while lookup in progress
316- this->engine.pCurrent = 0;
317+ ipAddrToAsciiGlobal *pGlobal = this->engine.pEngine;
318+ bool last;
319+ {
320+ epicsGuard < epicsMutex > guard ( pGlobal->mutex );
321+ while ( this->pending ) {
322+ if ( pGlobal->pCurrent == this &&
323+ pGlobal->callbackInProgress &&
324+ ! pGlobal->thread.isCurrentThread() ) {
325+ // cancel from another thread while callback in progress
326+ // waits for callback to complete
327+ assert ( pGlobal->cancelPendingCount < UINT_MAX );
328+ pGlobal->cancelPendingCount++;
329+ {
330+ epicsGuardRelease < epicsMutex > unguard ( guard );
331+ pGlobal->destructorBlockEvent.wait ();
332+ }
333+ assert ( pGlobal->cancelPendingCount > 0u );
334+ pGlobal->cancelPendingCount--;
335+ if ( ! this->pending ) {
336+ if ( pGlobal->cancelPendingCount ) {
337+ pGlobal->destructorBlockEvent.signal ();
338+ }
339+ break;
340+ }
341 }
342 else {
343- // cancel before lookup starts
344- this->engine.labor.remove ( *this );
345+ if ( pGlobal->pCurrent == this ) {
346+ // cancel from callback, or while lookup in progress
347+ pGlobal->pCurrent = 0;
348+ }
349+ else {
350+ // cancel before lookup starts
351+ pGlobal->labor.remove ( *this );
352+ }
353+ this->pending = false;
354 }
355- this->pending = false;
356 }
357+ assert(this->engine.refcount>0);
358+ last = 0==--this->engine.refcount;
359+ }
360+ if(last) {
361+ delete &this->engine;
362 }
363 }
364
365@@ -345,15 +429,21 @@
366 const osiSockAddr & addrIn, ipAddrToAsciiCallBack & cbIn )
367 {
368 bool success;
369+ ipAddrToAsciiGlobal *pGlobal = this->engine.pEngine;
370
371 {
372- epicsGuard < epicsMutex > guard ( this->engine.mutex );
373- // put some reasonable limit on queue expansion
374- if ( !this->pending && engine.labor.count () < 16u ) {
375+ epicsGuard < epicsMutex > guard ( pGlobal->mutex );
376+
377+ if (this->engine.released) {
378+ errlogPrintf("Warning: ipAddrToAscii on transaction with release()'d ipAddrToAsciiEngine");
379+ success = false;
380+
381+ } else if ( !this->pending && pGlobal->labor.count () < 16u ) {
382+ // put some reasonable limit on queue expansion
383 this->addr = addrIn;
384 this->pCB = & cbIn;
385 this->pending = true;
386- this->engine.labor.add ( *this );
387+ pGlobal->labor.add ( *this );
388 success = true;
389 }
390 else {
391@@ -362,7 +452,7 @@
392 }
393
394 if ( success ) {
395- this->engine.laborEvent.signal ();
396+ pGlobal->laborEvent.signal ();
397 }
398 else {
399 char autoNameTmp[256];
400@@ -379,7 +469,7 @@
401
402 void ipAddrToAsciiTransactionPrivate::show ( unsigned level ) const
403 {
404- epicsGuard < epicsMutex > guard ( this->engine.mutex );
405+ epicsGuard < epicsMutex > guard ( this->engine.pEngine->mutex );
406 char ipAddr [64];
407 sockAddrToDottedIP ( &this->addr.sa, ipAddr, sizeof ( ipAddr ) );
408 printf ( "ipAddrToAsciiTransactionPrivate for address %s\n", ipAddr );
409
410=== modified file 'src/libCom/misc/ipAddrToAsciiAsynchronous.h'
411--- src/libCom/misc/ipAddrToAsciiAsynchronous.h 2016-05-22 03:43:09 +0000
412+++ src/libCom/misc/ipAddrToAsciiAsynchronous.h 2017-02-27 16:54:43 +0000
413@@ -44,6 +44,10 @@
414 static ipAddrToAsciiEngine & allocate ();
415 protected:
416 virtual ~ipAddrToAsciiEngine () = 0;
417+public:
418+#ifdef EPICS_PRIVATE_API
419+ static void cleanup();
420+#endif
421 };
422
423 #endif // ifdef ipAddrToAsciiAsynchronous_h
424
425=== modified file 'src/libCom/test/Makefile'
426--- src/libCom/test/Makefile 2016-07-06 16:36:09 +0000
427+++ src/libCom/test/Makefile 2017-02-27 16:54:43 +0000
428@@ -151,6 +151,9 @@
429 testHarness_SRCS += epicsMessageQueueTest.cpp
430 TESTS += epicsMessageQueueTest
431
432+TESTPROD_HOST += ipAddrToAsciiTest
433+ipAddrToAsciiTest_SRCS += ipAddrToAsciiTest.cpp
434+TESTS += ipAddrToAsciiTest
435
436 # The testHarness runs all the test programs in a known working order.
437 testHarness_SRCS += epicsRunLibComTests.c
438
439=== added file 'src/libCom/test/ipAddrToAsciiTest.cpp'
440--- src/libCom/test/ipAddrToAsciiTest.cpp 1970-01-01 00:00:00 +0000
441+++ src/libCom/test/ipAddrToAsciiTest.cpp 2017-02-27 16:54:43 +0000
442@@ -0,0 +1,161 @@
443+/*************************************************************************\
444+* Copyright (c) 2017 Michael Davidsaver
445+* EPICS BASE is distributed subject to a Software License Agreement found
446+* in file LICENSE that is included with this distribution.
447+\*************************************************************************/
448+
449+#include <stdio.h>
450+#include <stdlib.h>
451+
452+#define EPICS_PRIVATE_API
453+
454+#include "epicsMutex.h"
455+#include "epicsGuard.h"
456+#include "epicsThread.h"
457+#include "epicsEvent.h"
458+#include "ipAddrToAsciiAsynchronous.h"
459+
460+#include "epicsUnitTest.h"
461+#include "testMain.h"
462+
463+namespace {
464+
465+typedef epicsGuard<epicsMutex> Guard;
466+typedef epicsGuardRelease<epicsMutex> UnGuard;
467+
468+struct CB : public ipAddrToAsciiCallBack
469+{
470+ const char *name;
471+ epicsMutex mutex;
472+ epicsEvent starter, blocker, complete;
473+ bool started, cont, done;
474+ CB(const char *name) : name(name), started(false), cont(false), done(false) {}
475+ virtual ~CB() {}
476+ virtual void transactionComplete ( const char * pHostName )
477+ {
478+ Guard G(mutex);
479+ started = true;
480+ starter.signal();
481+ testDiag("In transactionComplete(%s) for %s", pHostName, name);
482+ while(!cont) {
483+ UnGuard U(G);
484+ if(!blocker.wait(2.0))
485+ break;
486+ }
487+ done = true;
488+ complete.signal();
489+ }
490+ void waitStart()
491+ {
492+ Guard G(mutex);
493+ while(!started) {
494+ UnGuard U(G);
495+ if(!starter.wait(2.0))
496+ break;
497+ }
498+ }
499+ void poke()
500+ {
501+ testDiag("Poke");
502+ Guard G(mutex);
503+ cont = true;
504+ blocker.signal();
505+ }
506+ void finish()
507+ {
508+ testDiag("Finish");
509+ Guard G(mutex);
510+ while(!done) {
511+ UnGuard U(G);
512+ if(!complete.wait(2.0))
513+ break;
514+ }
515+ testDiag("Finished");
516+ }
517+};
518+
519+// ensure that lookup of 127.0.0.1 works
520+void doLookup(ipAddrToAsciiEngine& engine)
521+{
522+ testDiag("In doLookup");
523+
524+ ipAddrToAsciiTransaction& trn(engine.createTransaction());
525+ CB cb("cb");
526+ osiSockAddr addr;
527+ addr.ia.sin_family = AF_INET;
528+ addr.ia.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
529+ addr.ia.sin_port = htons(42);
530+
531+ testDiag("Start lookup");
532+ trn.ipAddrToAscii(addr, cb);
533+ cb.poke();
534+ cb.finish();
535+ testOk1(cb.cont);
536+ testOk1(cb.done);
537+
538+ trn.release();
539+}
540+
541+// Test cancel of pending transaction
542+void doCancel()
543+{
544+ testDiag("In doCancel");
545+
546+ ipAddrToAsciiEngine& engine1(ipAddrToAsciiEngine::allocate());
547+ ipAddrToAsciiEngine& engine2(ipAddrToAsciiEngine::allocate());
548+
549+ ipAddrToAsciiTransaction& trn1(engine1.createTransaction()),
550+ & trn2(engine2.createTransaction());
551+ testOk1(&trn1!=&trn2);
552+ CB cb1("cb1"), cb2("cb2");
553+
554+ osiSockAddr addr;
555+ addr.ia.sin_family = AF_INET;
556+ addr.ia.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
557+ addr.ia.sin_port = htons(42);
558+
559+ // ensure that the worker thread is blocked with a transaction from engine1
560+ testDiag("Start lookup1");
561+ trn1.ipAddrToAscii(addr, cb1);
562+ testDiag("Wait start1");
563+ cb1.waitStart();
564+
565+ testDiag("Start lookup2");
566+ trn2.ipAddrToAscii(addr, cb2);
567+
568+ testDiag("release engine2, implicitly cancels lookup2");
569+ engine2.release();
570+
571+ cb2.poke();
572+ testDiag("Wait for lookup2 timeout");
573+ cb2.finish();
574+ testOk1(!cb2.done);
575+
576+ testDiag("Complete lookup1");
577+ cb1.poke();
578+ cb1.finish();
579+ testOk1(cb1.done);
580+
581+ engine1.release();
582+
583+ trn1.release();
584+ trn2.release();
585+}
586+
587+} // namespace
588+
589+MAIN(ipAddrToAsciiTest)
590+{
591+ testPlan(5);
592+ {
593+ ipAddrToAsciiEngine& engine(ipAddrToAsciiEngine::allocate());
594+ doLookup(engine);
595+ engine.release();
596+ }
597+ doCancel();
598+ // TODO: somehow test cancel of in-progress callback
599+ // allow time for any un-canceled transcations to crash us...
600+ epicsThreadSleep(1.0);
601+ ipAddrToAsciiEngine::cleanup();
602+ return testDone();
603+}

Subscribers

People subscribed via source and target branches