Merge lp:~epics-core/epics-base/fix-async-dns into lp:~epics-core/epics-base/3.14
- fix-async-dns
- Merge into 3.14
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 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
Ralph Lange | Approve | ||
Review via email: mp+318385@code.launchpad.net |
Commit message
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 ipAddrToAsciiEn
The original code was still open to races when more than one call to ipAddrToAsciiEn
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 ipAddrToAsciiEn
Calling ipAddrToAsciiEn
Andrew Johnson (anj) wrote : | # |
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.
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 epicsRunLibComT
mdavidsaver (mdavidsaver) wrote : | # |
No reason, an oversight on my part. I'll add this evening if you haven't already.
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...
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.
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 epicsRunLibComT
0x00559584 runTestFunc +0x4c : ipAddrToAsciiTest ()
0x00547500 ipAddrToAsciiTe
0x00557b90 ipAddrToAsciiEn
0x0027c578 __cxa_throw +0x70 : std::terminate() ()
0x00279e64 __cxxabiv1:
0x00279e30 __cxxabiv1:
0x00133c70 cplusTerminate+0x4c : taskSuspend ()
Is the ipAddrToAsciiEn
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.
mdavidsaver (mdavidsaver) wrote : | # |
> Is the ipAddrToAsciiEn
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
mdavidsaver (mdavidsaver) wrote : | # |
@anj done
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:
../ipAddrToAsci
#include <valgrind/
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.
Preview Diff
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 | +} |
Subject to merge testing, accepted in principle at 3/14/2017 F2F meeting.