1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: threadtest.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_framework.hxx"
34 //_________________________________________________________________________________________________________________
36 //_________________________________________________________________________________________________________________
37 #include <macros/generic.hxx>
38 #include <macros/debug.hxx>
39 #include <threadhelp/resetableguard.hxx>
40 #include <threadhelp/transactionguard.hxx>
42 #ifndef __FRAMEWORK_THREADHELP_RWLOCKBASE_HXX_
43 #include <threadhelp/rwlockbase.hxx>
46 #ifndef __FRAMEWORK_THREADHELP_TRANSACTIONBASE_HXX_
47 #include <threadhelp/transactionbase.hxx>
49 #include <threadhelp/readguard.hxx>
50 #include <threadhelp/writeguard.hxx>
52 //_________________________________________________________________________________________________________________
54 //_________________________________________________________________________________________________________________
56 //_________________________________________________________________________________________________________________
58 //_________________________________________________________________________________________________________________
59 #include <rtl/random.h>
60 #include <vos/process.hxx>
61 #include <vos/thread.hxx>
62 #include <rtl/ustring.hxx>
63 #include <rtl/ustrbuf.hxx>
66 #ifndef _OSL_INTERLOCK_H_
67 #include <osl/interlock.h>
70 #include <vcl/event.hxx>
71 #include <vcl/svapp.hxx>
72 #include <vcl/wrkwin.hxx>
73 #include <vcl/msgbox.hxx>
76 //_________________________________________________________________________________________________________________
78 //_________________________________________________________________________________________________________________
80 #define LOGFILE "threadtest.log"
81 #define STATISTICS_FILE "threadtest_statistic.csv"
83 //_________________________________________________________________________________________________________________
85 //_________________________________________________________________________________________________________________
87 using namespace ::rtl
;
88 using namespace ::osl
;
89 using namespace ::vos
;
90 using namespace ::framework
;
92 //_________________________________________________________________________________________________________________
94 //_________________________________________________________________________________________________________________
96 /*---------------- Use follow defines to enable/disable some special features of this little test program! -------*/
99 //#define ENABLE_THREADDELAY
100 #define ENABLE_REQUESTCOUNT
102 /*----------------------------------------------------------------------------------------------------------------*/
105 #define LOG_SETA_START( NA, NID ) \
107 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
108 ResetableGuard aLogGuard( m_aLogMutex ); \
109 OStringBuffer sLog(256); \
110 sLog.append( (sal_Int32)nTimeStamp ); \
111 sLog.append( ": Thread[ " ); \
112 sLog.append( NID ); \
113 sLog.append( " ] call setA( " ); \
115 sLog.append( " )\n" ); \
116 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
119 #define LOG_SETA_END( NA, EREASON, NID ) \
121 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
122 ResetableGuard aLogGuard( m_aLogMutex ); \
123 OStringBuffer sLog(256); \
124 sLog.append( (sal_Int32)nTimeStamp ); \
125 sLog.append( ": Thread[ " ); \
126 sLog.append( NID ); \
127 if( EREASON == E_NOREASON ) \
128 sLog.append( " ] finish setA( " ); \
130 sLog.append( " ] was refused at setA( "); \
132 sLog.append( " )\n" ); \
133 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
136 #define LOG_GETA_START( NID ) \
138 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
139 ResetableGuard aLogGuard( m_aLogMutex ); \
140 OStringBuffer sLog(256); \
141 sLog.append( (sal_Int32)nTimeStamp ); \
142 sLog.append( ": Thread[ " ); \
143 sLog.append( NID ); \
144 sLog.append( " ] call getA()\n" ); \
145 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
148 #define LOG_GETA_END( NRETURN, EREASON, NID ) \
150 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
151 ResetableGuard aLogGuard( m_aLogMutex ); \
152 OStringBuffer sLog(256); \
153 sLog.append( (sal_Int32)nTimeStamp ); \
154 sLog.append( ": Thread[ " ); \
155 sLog.append( NID ); \
156 if( EREASON == E_NOREASON ) \
157 sLog.append( " ] finish getA() with " ); \
159 sLog.append( " ] was refused at getA() with " ); \
160 sLog.append( NRETURN ); \
161 sLog.append( "\n" ); \
162 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
165 #define LOG_WORKA_START( NA, NID ) \
167 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
168 ResetableGuard aLogGuard( m_aLogMutex ); \
169 OStringBuffer sLog(256); \
170 sLog.append( (sal_Int32)nTimeStamp ); \
171 sLog.append( ": Thread[ " ); \
172 sLog.append( NID ); \
173 sLog.append( " ] call workA( " ); \
175 sLog.append( " )\n" ); \
176 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
179 #define LOG_WORKA_END( NRETURN, EREASON, NID ) \
181 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
182 ResetableGuard aLogGuard( m_aLogMutex ); \
183 OStringBuffer sLog(256); \
184 sLog.append( (sal_Int32)nTimeStamp ); \
185 sLog.append( ": Thread[ " ); \
186 sLog.append( NID ); \
187 if( EREASON == E_NOREASON ) \
188 sLog.append( " ] finish workA() with " ); \
190 sLog.append( " ] was refused at workA() with " ); \
191 sLog.append( NRETURN ); \
192 sLog.append( "\n" ); \
193 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
196 #define LOG_INITEXCEPTION( SMETHOD, NID ) \
198 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
199 ResetableGuard aLogGuard( m_aLogMutex ); \
200 OStringBuffer sLog(256); \
201 sLog.append( (sal_Int32)nTimeStamp ); \
202 sLog.append( ": Thread[ " ); \
203 sLog.append( NID ); \
204 sLog.append( " ] get EInitException from \"" ); \
205 sLog.append( SMETHOD ); \
206 sLog.append( "\"\n" ); \
207 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
210 #define LOG_CLOSEEXCEPTION( SMETHOD, NID ) \
212 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
213 ResetableGuard aLogGuard( m_aLogMutex ); \
214 OStringBuffer sLog(256); \
215 sLog.append( (sal_Int32)nTimeStamp ); \
216 sLog.append( ": Thread[ " ); \
217 sLog.append( NID ); \
218 sLog.append( " ] get ECloseException from \"" ); \
219 sLog.append( SMETHOD ); \
220 sLog.append( "\"\n" ); \
221 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
224 #define LOG_INIT( NA, NID ) \
226 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
227 ResetableGuard aLogGuard( m_aLogMutex ); \
228 OStringBuffer sLog(256); \
229 sLog.append( (sal_Int32)nTimeStamp ); \
230 sLog.append( ": Thread[ " ); \
231 sLog.append( NID ); \
232 sLog.append( " ] initialize me with " ); \
234 sLog.append( "\n" ); \
235 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
238 #define LOG_CLOSE( NID ) \
240 sal_uInt32 nTimeStamp = osl_getGlobalTimer(); \
241 ResetableGuard aLogGuard( m_aLogMutex ); \
242 OStringBuffer sLog(256); \
243 sLog.append( (sal_Int32)nTimeStamp ); \
244 sLog.append( ": Thread[ " ); \
245 sLog.append( NID ); \
246 sLog.append( " ] close me\n" ); \
247 WRITE_LOGFILE( LOGFILE, sLog.makeStringAndClear() ) \
250 #define LOG_SETA_START( NA, NID )
251 #define LOG_SETA_END( NA, EREASON, NID )
252 #define LOG_GETA_START( NID )
253 #define LOG_GETA_END( NRETURN, EREASON, NID )
254 #define LOG_WORKA_START( NA, NID )
255 #define LOG_WORKA_END( NRETURN, EREASON, NID )
256 #define LOG_INITEXCEPTION( SMETHOD, NID )
257 #define LOG_CLOSEEXCEPTION( SMETHOD, NID )
258 #define LOG_INIT( NA, NID )
259 #define LOG_CLOSE( NID )
262 //_________________________________________________________________________________________________________________
264 //_________________________________________________________________________________________________________________
266 sal_uInt16
getRandomValue()
268 // Get new random value for thread-sleep!
269 // See run() for further informations.
270 // Always calculate a new random number.
272 rtlRandomPool aPool
= rtl_random_createPool();
273 rtl_random_getBytes ( aPool
, &nValue
, 2 );
274 rtl_random_destroyPool ( aPool
);
278 /*-************************************************************************************************************//**
279 @descr This class is used from different threads at the same time.
280 We start working after calling init() first(!) ...
281 and finish it by calling close(). It exist two methods for reading/writing an
282 internal variable "A". Another function workA() do both things at the same time.
283 All public methods log information in a file if DO_LOG is defined.
285 @attention Our public base class FaiRWLockBase is a struct with a RWLock as member.
286 This member can be used by guards to safe access at internal variables
287 in interface methods.
288 Another baseclass is the TransactionBase. They support rejection of wrong calls at wrong time.
289 e.g. calls after closing object!
290 *//*-*************************************************************************************************************/
292 class ThreadSafeClass
: private TransactionBase
293 , private FairRWLockBase
300 // This methods are used from differnt threads
301 // to test this class.
302 void init ( sal_Int32 nA
,
303 sal_Int32 nThreadID
);
304 void close ( sal_Int32 nThreadID
);
305 void setA ( sal_Int32 nA
,
306 sal_Int32 nThreadID
);
307 sal_Int32
getA ( sal_Int32 nThreadID
);
308 sal_Int32
workA ( sal_Int32 nA
,
309 sal_Int32 nThreadID
);
311 #ifdef ENABLE_REQUESTCOUNT
312 // This methods are used for statistics only!
313 sal_Int32
getReadCount () { return m_nReadCount
; }
314 sal_Int32
getWriteCount() { return m_nWriteCount
; }
319 sal_Int32 m_nA
; /// test member fro reading/writing
322 ::osl::Mutex m_aLogMutex
; /// mutex to serialize writing log file!
325 #ifdef ENABLE_REQUESTCOUNT
326 oslInterlockedCount m_nReadCount
; /// statistic variables to count read/write requests
327 oslInterlockedCount m_nWriteCount
;
331 //_________________________________________________________________________________________________________________
332 ThreadSafeClass::ThreadSafeClass()
333 : TransactionBase ( )
336 #ifdef ENABLE_REQUESTCOUNT
338 , m_nWriteCount ( 0 )
343 //_________________________________________________________________________________________________________________
344 ThreadSafeClass::~ThreadSafeClass()
348 //_________________________________________________________________________________________________________________
349 void ThreadSafeClass::init( sal_Int32 nA
, sal_Int32 nThreadID
)
351 // Set write lock for setting internal member AND
352 // protect changing of working mode!
353 WriteGuard
aWriteLock( m_aLock
);
355 LOG_INIT( nA
, nThreadID
)
357 // Look for multiple calls of this method first!
358 // Use E_SOFTEXCEPTIONS to disable automaticly throwing of exceptions for some working modes.
359 ERejectReason eReason
;
360 TransactionGuard
aTransaction( m_aTransactionManager
, E_NOEXCEPTIONS
, eReason
);
361 if( eReason
== E_UNINITIALIZED
)
363 // OK, it must be the first call and we are synchronized with all other threads by using the write lock!
364 // Otherwise (e.g. if working mode == E_WORK) we get a exception and follow lines are never called.
366 // We can set our member and change the working mode now.
368 m_aTransactionManager
.setWorkingMode( E_WORK
);
372 //_________________________________________________________________________________________________________________
373 void ThreadSafeClass::close( sal_Int32 nThreadID
)
375 // Make it threadsafe.
376 // It must be an exclusiv access! => WriteLock!
377 WriteGuard
aWriteLock( m_aLock
);
379 LOG_CLOSE( nThreadID
)
381 // We must look for multiple calls of this method.
382 // Try to register this method as a transaction.
383 // In combination with E_HARDEXCEPTIONS only working mode E_WORK pass this barrier.
384 ERejectReason eReason
;
385 TransactionGuard
aTransaction( m_aTransactionManager
, E_NOEXCEPTIONS
, eReason
);
386 if( eReason
== E_NOREASON
)
388 // Change working mode to BEFORECLOSE to enable rejection of normal interface calls
389 // and enable SOFTEXCEPTION mode for some impl- or helper methods!
390 // Attention: We must stop successful registered transaction first ...
391 // because setWorkingMode() blocks and wait for all current existing ones!
393 m_aTransactionManager
.setWorkingMode( E_BEFORECLOSE
);
395 // Now we are alone ...
396 // All further calls to this object are rejected ...
397 // (not all ... some special ones can work by using E_SOFTEXCEPTIONS!)
399 // Deinitialize all member and set working mode to E_CLOSE.
401 m_aTransactionManager
.setWorkingMode( E_CLOSE
);
405 //_________________________________________________________________________________________________________________
406 void ThreadSafeClass::setA( sal_Int32 nA
, sal_Int32 nThreadID
)
408 // Make it threadsafe.
409 WriteGuard
aWriteLock( m_aLock
);
411 LOG_SETA_START( nA
, nThreadID
)
413 // Register this method as a transaction to prevent code against wrong calls
414 // after close() or before init()!
415 ERejectReason eReason
;
416 TransactionGuard
aTransaction( m_aTransactionManager
, E_NOEXCEPTIONS
, eReason
);
417 if( eReason
== E_NOREASON
)
419 // This object is ready for working and we have full write access.
420 // We can work with our member.
422 #ifdef ENABLE_REQUESTCOUNT
423 osl_incrementInterlockedCount( &m_nWriteCount
);
426 LOG_SETA_END( nA
, eReason
, nThreadID
)
429 //_________________________________________________________________________________________________________________
430 sal_Int32
ThreadSafeClass::getA( sal_Int32 nThreadID
)
432 // Make it threadsafe.
433 ReadGuard
aReadLock( m_aLock
);
435 LOG_GETA_START( nThreadID
)
437 // Register this method as a transaction to prevent code against wrong calls
438 // after close() or before init()!
439 sal_Int32 nReturn
= 0;
440 ERejectReason eReason
;
441 TransactionGuard
aTransaction( m_aTransactionManager
, E_NOEXCEPTIONS
, eReason
);
442 if( eReason
== E_NOREASON
)
444 // This object is ready for working and we have a read access.
445 // We can work with our member.
447 #ifdef ENABLE_REQUESTCOUNT
448 osl_incrementInterlockedCount( &m_nReadCount
);
452 LOG_GETA_END( nReturn
, eReason
, nThreadID
)
456 //_________________________________________________________________________________________________________________
457 sal_Int32
ThreadSafeClass::workA( sal_Int32 nA
,
458 sal_Int32 nThreadID
)
460 // This method test the downgrade-mechanism of used lock implementation!
461 // Make it threadsafe.
462 WriteGuard
aWriteLock( m_aLock
);
464 LOG_WORKA_START( nA
, nThreadID
)
466 // Register this method as a transaction to prevent code against wrong calls
467 // after close() or before init()!
468 sal_Int32 nReturn
= 0;
469 ERejectReason eReason
;
470 TransactionGuard
aTransaction( m_aTransactionManager
, E_NOEXCEPTIONS
, eReason
);
471 if( eReason
== E_NOREASON
)
473 // We have write access to our member.
476 #ifdef ENABLE_REQUESTCOUNT
477 osl_incrementInterlockedCount( &m_nWriteCount
);
480 // Downgrade write access to read access and read the set value again.
481 // This call can't be rejected - but it can fail!
482 aWriteLock
.downgrade();
484 #ifdef ENABLE_REQUESTCOUNT
485 osl_incrementInterlockedCount( &m_nReadCount
);
489 LOG_WORKA_END( nReturn
, eReason
, nThreadID
)
493 /*-****************************************************************************************************//**
494 @descr Every thread instance of these class lopp from 0 up to "nLoops".
495 He sleep for a random time and work with given test class "pClass" then.
496 We use random values for waiting for better results!
497 Otherwise all threads are sychron after first 2,3...5 calls - I think!
498 *//*-*****************************************************************************************************/
500 class TestThread
: public OThread
504 TestThread( ThreadSafeClass
* pClass
,
506 Condition
* pListener
,
507 sal_Bool bOwner
= sal_False
);
511 virtual void SAL_CALL
run ();
512 virtual void SAL_CALL
onTerminated ();
516 ThreadSafeClass
* m_pClass
;
518 sal_Int32 m_nThreadID
;
519 Condition
* m_pListener
;
523 //_________________________________________________________________________________________________________________
524 TestThread::TestThread( ThreadSafeClass
* pClass
,
526 Condition
* pListener
,
528 : m_pClass ( pClass
)
529 , m_nLoops ( nLoops
)
530 , m_pListener ( pListener
)
531 , m_bOwner ( bOwner
)
535 //_________________________________________________________________________________________________________________
536 void SAL_CALL
TestThread::run()
538 // Get ID of this thread.
539 // Is used for logging information ...
540 m_nThreadID
= getCurrentIdentifier();
542 // If we are the owner of given pClass
543 // we must initialize ... and close
544 // it. See at the end of this method too.
545 if( m_bOwner
== sal_True
)
547 m_pClass
->init( 0, m_nThreadID
);
550 #ifdef ENABLE_THREADDELAY
556 for( sal_Int32 nCount
=0; nCount
<m_nLoops
; ++nCount
)
559 // Use random to select called method.
560 nA
= (sal_Int32
)getRandomValue();
563 nA
= m_pClass
->workA( nA
, m_nThreadID
);
568 m_pClass
->setA( nA
, m_nThreadID
);
572 nA
= m_pClass
->getA( m_nThreadID
);
574 #ifdef ENABLE_THREADDELAY
575 // Sleep - use random value to do that too!
577 nDelay
.Nanosec
= getRandomValue();
582 // Don't forget to "close" teset object if you are the owner!
583 if( m_bOwner
== sal_True
)
585 m_pClass
->close( m_nThreadID
);
589 //_________________________________________________________________________________________________________________
590 void SAL_CALL
TestThread::onTerminated()
592 // Destroy yourself if you finished.
593 // But don't forget to call listener before.
602 /*-****************************************************************************************************//**
603 @descr This is our test application.
604 We create one ThreadSafeClass object and a lot of threads
605 which use it at different times.
606 *//*-*****************************************************************************************************/
610 Condition
* pCondition
;
611 TestThread
* pThread
;
614 class TestApplication
: public Application
618 sal_Int32
measureTime ( sal_Int32 nThreadCount
,
620 sal_Int32 nLoops
=0 );
623 //_________________________________________________________________________________________________________________
625 //_________________________________________________________________________________________________________________
627 TestApplication aApplication
;
629 //_________________________________________________________________________________________________________________
630 // This function start "nThreadCount" threads to use same test class.
631 // You can specify the owner thread of this test class which start/stop it by using "nOwner". [1..nThreadcount]!
632 // If you specify "nLoops" different from 0 we use it as loop count for every started thread.
633 // Otherwise we work with random values.
634 sal_Int32
TestApplication::measureTime( sal_Int32 nThreadCount
,
638 // This is the class which should be tested.
639 ThreadSafeClass aClass
;
641 // Create list of threads.
642 ThreadInfo
* pThreads
= new ThreadInfo
[nThreadCount
];
643 sal_Int32 nLoopCount
= nLoops
;
644 sal_Bool bOwner
= sal_False
;
645 for( sal_Int32 nI
=1; nI
<=nThreadCount
; ++nI
)
647 // If nLoops==0 => we must use random value; otherwise we must use given count ...
650 nLoopCount
= getRandomValue();
652 // Search owner of class.
658 // initialize condition.
659 pThreads
[nI
].pCondition
= new Condition
;
660 // Initialize thread.
661 pThreads
[nI
].pThread
= new TestThread( &aClass
, nLoopCount
, pThreads
[nI
].pCondition
, bOwner
);
664 // Start clock to get information about used time.
665 sal_uInt32 nStartTime
;
666 sal_uInt32 nEndTime
;
668 nStartTime
= osl_getGlobalTimer();
671 for( nI
=1; nI
<=nThreadCount
; ++nI
)
673 pThreads
[nI
].pThread
->create();
676 // Wait for threads ...
677 for( nI
=1; nI
<=nThreadCount
; ++nI
)
679 pThreads
[nI
].pCondition
->wait();
680 delete pThreads
[nI
].pCondition
;
681 pThreads
[nI
].pCondition
= NULL
;
687 nEndTime
= osl_getGlobalTimer();
689 // Calc used time and return it. [ms]
690 return( nEndTime
-nStartTime
);
693 //_________________________________________________________________________________________________________________
694 void TestApplication::Main()
696 sal_Int32 nTestCount
= 0; /// count of calling "measureTime()"
697 sal_Int32 nThreadCount
= 0; /// count of used threads by "measure..."
698 sal_Int32 nLoops
= 0; /// loop count for every thread
699 sal_Int32 nOwner
= 0; /// number of owner thread
701 // Parse command line.
702 // Attention: All parameter are required and must exist!
703 // syntax: "threadtest.exe <testcount> <threadcount> <loops> <owner>"
706 sal_Int32 nArgument
;
707 sal_Int32 nCount
= aInfo
.getCommandArgCount();
709 LOG_ASSERT2( nCount
!=4 ,"TestApplication::Main()" , "Wrong argument line detected!")
711 for( nArgument
=0; nArgument
<nCount
; ++nArgument
)
713 aInfo
.getCommandArg( nArgument
, sArgument
);
714 if( nArgument
== 0 ) nTestCount
=sArgument
.toInt32();
715 if( nArgument
== 1 ) nThreadCount
=sArgument
.toInt32();
716 if( nArgument
== 2 ) nLoops
=sArgument
.toInt32();
717 if( nArgument
== 3 ) nOwner
=sArgument
.toInt32();
721 OStringBuffer
sBuf(256);
723 sBuf
.append( "Nr.\tTime\tThreadCount\tLoops\tOwner\n" );
724 for( sal_Int32 nI
=1; nI
<=nTestCount
; ++nI
)
726 nTime
= measureTime( nThreadCount
, nOwner
, nLoops
);
729 sBuf
.append( nTime
);
731 sBuf
.append( nThreadCount
);
733 sBuf
.append( nLoops
);
735 sBuf
.append( nOwner
);
739 WRITE_LOGFILE( STATISTICS_FILE
, sBuf
.makeStringAndClear() );
740 LOG_ERROR( "TApplication::Main()", "Test finish successful!" )