From cda189760da33415c931da8cdc5fb2285be07436 Mon Sep 17 00:00:00 2001 From: Kimmo Hoikka Date: Tue, 30 Jun 2015 16:00:41 +0100 Subject: [PATCH] ConditionalWait to replace boost conditional variable usage Change-Id: I337a7c0577056bf40e4a3cddd1cc100f6d5af7af --- adaptors/base/conditional-wait.cpp | 104 ++++++++++++ adaptors/base/conditional-wait.h | 85 ++++++++++ adaptors/base/file.list | 1 + .../src/dali-adaptor-internal/CMakeLists.txt | 8 +- .../dali-adaptor-internal/utc-ConditionalWait.cpp | 177 +++++++++++++++++++++ .../dali-test-suite-utils.cpp | 44 +---- .../dali-test-suite-utils/dali-test-suite-utils.h | 25 +-- .../dali-test-suite-utils/test-harness.cpp | 22 ++- build/tizen/adaptor/Makefile.am | 1 + 9 files changed, 397 insertions(+), 70 deletions(-) create mode 100644 adaptors/base/conditional-wait.cpp create mode 100644 adaptors/base/conditional-wait.h create mode 100644 automated-tests/src/dali-adaptor-internal/utc-ConditionalWait.cpp diff --git a/adaptors/base/conditional-wait.cpp b/adaptors/base/conditional-wait.cpp new file mode 100644 index 0000000..cb0b16c --- /dev/null +++ b/adaptors/base/conditional-wait.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include "conditional-wait.h" + +// EXTERNAL INCLUDES +#include + +namespace Dali +{ + +namespace Internal +{ + +namespace Adaptor +{ + +namespace +{ +} // unnamed namespace + +struct ConditionalWait::ConditionalWaitImpl +{ + pthread_mutex_t mutex; + pthread_cond_t condition; + volatile bool wait; +}; + +ConditionalWait::ConditionalWait() +: mImpl( new ConditionalWaitImpl ) +{ + pthread_mutex_init( &mImpl->mutex, NULL ); + pthread_cond_init( &mImpl->condition, NULL ); + mImpl->wait = false; +} + +ConditionalWait::~ConditionalWait() +{ + pthread_cond_destroy( &mImpl->condition ); + pthread_mutex_destroy( &mImpl->mutex ); + delete mImpl; +} + +void ConditionalWait::Notify() +{ + // pthread_cond_wait requires a lock to be held + pthread_mutex_lock( &mImpl->mutex ); + bool wasWaiting = mImpl->wait; + mImpl->wait = false; + pthread_mutex_unlock( &mImpl->mutex ); + // broadcast does nothing if the thread is not waiting but still has a system call overhead + // broadcast all threads to continue + if( wasWaiting ) + { + pthread_cond_broadcast( &mImpl->condition ); + } +} + +void ConditionalWait::Wait() +{ + // pthread_cond_wait requires a lock to be held + pthread_mutex_lock( &mImpl->mutex ); + mImpl->wait = true; + // pthread_cond_wait may wake up without anyone calling Notify + while( mImpl->wait ) + { + // wait while condition changes + pthread_cond_wait( &mImpl->condition, &mImpl->mutex ); // releases the lock whilst waiting + } + // when condition returns the mutex is locked so release the lock + pthread_mutex_unlock( &mImpl->mutex ); +} + +bool ConditionalWait::IsWaiting() const +{ + bool isWaiting( false ); + pthread_mutex_lock( &mImpl->mutex ); + isWaiting = mImpl->wait; + pthread_mutex_unlock( &mImpl->mutex ); + return isWaiting; +} + + + +} // namespace Adaptor + +} // namespace Internal + +} // namespace Dali diff --git a/adaptors/base/conditional-wait.h b/adaptors/base/conditional-wait.h new file mode 100644 index 0000000..3638bfa --- /dev/null +++ b/adaptors/base/conditional-wait.h @@ -0,0 +1,85 @@ +#ifndef __DALI_INTERNAL_CONDITIONAL_WAIT_H__ +#define __DALI_INTERNAL_CONDITIONAL_WAIT_H__ + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +namespace Dali +{ + +namespace Internal +{ + +namespace Adaptor +{ + +/** + * Helper class to allow conditional waiting and notifications between multiple threads + */ +class ConditionalWait +{ +public: + + /** + * @brief Constructor, creates the internal synchronization objects + */ + ConditionalWait(); + + /** + * @brief Destructor, non-virtual as this is not a base class + */ + ~ConditionalWait(); + + /** + * @brief Notifies another thread to continue if it is blocked on a wait. + * + * Can be called from any thread. + * Does not block the current thread but may cause a rescheduling of threads. + */ + void Notify(); + + /** + * @brief Wait for another thread to notify us when the condition is true and we can continue + * + * Will always block current thread until Notify is called + */ + void Wait(); + + /** + * @brief Return true if the wait is locked, i.e. someone is waiting for it + * @return true if this object is waiting on a thread + */ + bool IsWaiting() const; + +private: + + /// Not implemented as ConditionalWait is not copyable + ConditionalWait( const ConditionalWait& ); + const ConditionalWait& operator= ( const ConditionalWait& ); + + struct ConditionalWaitImpl; + ConditionalWaitImpl* mImpl; + +}; + +} // namespace Adaptor + +} // namespace Internal + +} // namespace Dali + +#endif // __DALI_INTERNAL_CONDITIONAL_WAIT_H__ diff --git a/adaptors/base/file.list b/adaptors/base/file.list index de1452d..f787a18 100644 --- a/adaptors/base/file.list +++ b/adaptors/base/file.list @@ -1,6 +1,7 @@ # Add local source files here base_adaptor_src_files = \ + $(base_adaptor_src_dir)/conditional-wait.cpp \ $(base_adaptor_src_dir)/frame-time.cpp \ $(base_adaptor_src_dir)/display-connection.cpp \ $(base_adaptor_src_dir)/render-thread.cpp \ diff --git a/automated-tests/src/dali-adaptor-internal/CMakeLists.txt b/automated-tests/src/dali-adaptor-internal/CMakeLists.txt index fc8854c..f35aeb7 100644 --- a/automated-tests/src/dali-adaptor-internal/CMakeLists.txt +++ b/automated-tests/src/dali-adaptor-internal/CMakeLists.txt @@ -6,11 +6,12 @@ SET(RPM_NAME "core-${PKG_NAME}-tests") SET(CAPI_LIB "dali-adaptor-internal") SET(TC_SOURCES - utc-Dali-GifLoader.cpp - utc-Dali-TiltSensor.cpp + utc-ConditionalWait.cpp utc-Dali-CommandLineOptions.cpp - utc-Dali-Lifecycle-Controller.cpp + utc-Dali-GifLoader.cpp utc-Dali-ImageOperations.cpp + utc-Dali-Lifecycle-Controller.cpp + utc-Dali-TiltSensor.cpp ) LIST(APPEND TC_SOURCES @@ -65,6 +66,7 @@ INCLUDE_DIRECTORIES( ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.cpp ${TC_SOURCES}) TARGET_LINK_LIBRARIES(${EXEC_NAME} ${${CAPI_LIB}_LIBRARIES} + -lpthread ) INSTALL(PROGRAMS ${EXEC_NAME} diff --git a/automated-tests/src/dali-adaptor-internal/utc-ConditionalWait.cpp b/automated-tests/src/dali-adaptor-internal/utc-ConditionalWait.cpp new file mode 100644 index 0000000..85abcf6 --- /dev/null +++ b/automated-tests/src/dali-adaptor-internal/utc-ConditionalWait.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include "conditional-wait.h" + +using Dali::Internal::Adaptor::ConditionalWait; + +namespace // for local variables to avoid name clashes +{ +volatile int gGlobalValue = 0; +volatile bool gWorkerThreadWait = true; +enum ThreadState { INIT, RUN, TERMINATE } volatile gWorkerThreadState = INIT; +ConditionalWait* volatile gConditionalWait; // volatile pointer to a ConditionalWait object +} + +void* WorkerThreadNotify( void* ptr ) +{ + gGlobalValue = -1; + while( gWorkerThreadWait ) // wait till we can exit + { + gWorkerThreadState = RUN; + usleep( 1 ); // 1 microseconds + } + usleep( 1000 ); // 1000 microseconds to give other thread time to do its thing + gConditionalWait->Notify(); + gWorkerThreadState = TERMINATE; + return NULL; +} + +int UtcConditionalWait1P(void) +{ + tet_infoline("Testing ConditionalWait - scenario 1: wait - notify from separate thread"); + + pthread_t thread1; + // initialize values + gConditionalWait = new ConditionalWait(); + gWorkerThreadWait = true; + DALI_TEST_EQUALS( INIT, gWorkerThreadState, TEST_LOCATION ); + DALI_TEST_EQUALS( 0, gGlobalValue, TEST_LOCATION ); + + pthread_create( &thread1, NULL, &WorkerThreadNotify, NULL ); + // wait till the thread is in run state + while( RUN != gWorkerThreadState ) + { + usleep( 1 ); // 1 microsecond + } + // let worker continue and finish + gWorkerThreadWait = false; + gConditionalWait->Wait(); + + // wait till the thread is terminated state + while( TERMINATE != gWorkerThreadState ) + { + usleep( 1 ); // 1 microsecond + } + + void* exitValue; + pthread_join( thread1, &exitValue ); + + delete gConditionalWait; + END_TEST; +} + +int UtcConditionalWait2P(void) +{ + tet_infoline("Testing ConditionalWait - scenario 2: notify without wait"); + + ConditionalWait wait; + DALI_TEST_EQUALS( false, wait.IsWaiting(), TEST_LOCATION ); + wait.Notify(); + DALI_TEST_EQUALS( false, wait.IsWaiting(), TEST_LOCATION ); + + END_TEST; +} + +volatile unsigned int gNotifyCount = 0; +void* WorkerThreadNotifyN( void* ptr ) +{ + while( gNotifyCount ) + { + gConditionalWait->Notify(); + usleep( 10 ); // 10 microseconds between each notify + } + return NULL; +} + +int UtcConditionalWait3P(void) +{ + tet_infoline("Testing ConditionalWait - scenario 1: wait - notify N times"); + + // initialize values + gConditionalWait = new ConditionalWait(); + gNotifyCount = 100; + + pthread_t thread1; + pthread_create( &thread1, NULL, &WorkerThreadNotifyN, NULL ); + + while( gNotifyCount ) + { + gConditionalWait->Wait(); + --gNotifyCount; + DALI_TEST_EQUALS( false, gConditionalWait->IsWaiting(), TEST_LOCATION ); + usleep( 10 ); // 10 microseconds between each notify + } + DALI_TEST_EQUALS( false, gConditionalWait->IsWaiting(), TEST_LOCATION ); + + void* exitValue; + pthread_join( thread1, &exitValue ); + + delete gConditionalWait; + END_TEST; +} + +int UtcConditionalWait4P(void) +{ + tet_infoline("Testing ConditionalWait - scenario 1: wait - notify N times from multiple threads"); + + // initialize values + gConditionalWait = new ConditionalWait(); + gNotifyCount = 100; + + pthread_t thread1; + pthread_create( &thread1, NULL, &WorkerThreadNotifyN, NULL ); + + pthread_t thread2; + pthread_create( &thread2, NULL, &WorkerThreadNotifyN, NULL ); + + pthread_t thread3; + pthread_create( &thread3, NULL, &WorkerThreadNotifyN, NULL ); + + while( gNotifyCount ) + { + gConditionalWait->Wait(); + --gNotifyCount; + DALI_TEST_EQUALS( false, gConditionalWait->IsWaiting(), TEST_LOCATION ); + usleep( 10 ); // 10 microseconds between each notify + } + + void* exitValue; + pthread_join( thread1, &exitValue ); + pthread_join( thread2, &exitValue ); + pthread_join( thread3, &exitValue ); + + delete gConditionalWait; + END_TEST; +} + + +int UtcConditionalWaitNonCopyable(void) +{ + // we want to make sure that ConditionalWait is not copyable (its copy constructor is not defined) + // this test will stop compiling if ConditionalWait has compiler generated copy constructor + DALI_COMPILE_TIME_ASSERT( !__has_trivial_copy( ConditionalWait ) ); + + DALI_TEST_CHECK( true ); + END_TEST; +} + + diff --git a/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.cpp b/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.cpp index 9137f3d..f2cf564 100644 --- a/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.cpp +++ b/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.cpp @@ -19,7 +19,6 @@ #include "dali-test-suite-utils.h" // EXTERNAL INCLUDES -#include #include #include @@ -102,7 +101,7 @@ void DALI_TEST_EQUALS( const Matrix3& matrix1, const Matrix3& matrix2, const cha for (int i=0;i<9;++i) { - equivalent &= (m1[i] != m2[i]); + equivalent &= (fabsf(m1[i] - m2[i])< GetRangedEpsilon(m1[i], m2[i])); } if (!equivalent) @@ -244,47 +243,6 @@ void DALI_TEST_EQUALS( const char* str1, const std::string &str2, const char* lo DALI_TEST_EQUALS(str1, str2.c_str(), location); } - -/** - * Test whether one unsigned integer value is greater than another. - * Test succeeds if value1 > value2 - * @param[in] value1 The first value - * @param[in] value2 The second value - * @param[in] location The TEST_LOCATION macro should be used here - */ -void DALI_TEST_GREATER(unsigned int value1, unsigned int value2, const char* location) -{ - if (!(value1 > value2)) - { - fprintf(stderr, "%s, checking %d > %d\n", location, value1, value2); - tet_result(TET_FAIL); - } - else - { - tet_result(TET_PASS); - } -} - -/** - * Test whether one float value is greater than another. - * Test succeeds if value1 > value2 - * @param[in] value1 The first value - * @param[in] value2 The second value - * @param[in] location The TEST_LOCATION macro should be used here - */ -void DALI_TEST_GREATER( float value1, float value2, const char* location) -{ - if (!(value1 > value2)) - { - fprintf(stderr, "%s, checking %f > %f\n", location, value1, value2); - tet_result(TET_FAIL); - } - else - { - tet_result(TET_PASS); - } -} - void DALI_TEST_ASSERT( DaliException& e, std::string conditionSubString, const char* location ) { if( NULL == strstr( e.condition, conditionSubString.c_str() ) ) diff --git a/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.h b/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.h index 37f698c..8163028 100644 --- a/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.h +++ b/automated-tests/src/dali-adaptor/dali-test-suite-utils/dali-test-suite-utils.h @@ -20,7 +20,7 @@ // EXTERNAL INCLUDES #include -#include +#include // INTERNAL INCLUDES #include @@ -307,16 +307,19 @@ void DALI_TEST_EQUALS( const char* str1, const std::string &str2, const char* lo * @param[in] value2 The second value * @param[in] location The TEST_LOCATION macro should be used here */ -void DALI_TEST_GREATER(unsigned int value1, unsigned int value2, const char* location); - -/** - * Test whether one float value is greater than another. - * Test succeeds if value1 > value2 - * @param[in] value1 The first value - * @param[in] value2 The second value - * @param[in] location The TEST_LOCATION macro should be used here - */ -void DALI_TEST_GREATER( float value1, float value2, const char* location); +template< typename T > +void DALI_TEST_GREATER(unsigned int value1, unsigned int value2, const char* location) +{ + if (!(value1 > value2)) + { + std::cerr << location << ", checking " << value1 <<" > " << value2 << "\n"; + tet_result(TET_FAIL); + } + else + { + tet_result(TET_PASS); + } +} /** * Test whether the assertion condition that failed and thus triggered the diff --git a/automated-tests/src/dali-adaptor/dali-test-suite-utils/test-harness.cpp b/automated-tests/src/dali-adaptor/dali-test-suite-utils/test-harness.cpp index 3fed0a2..475b62a 100644 --- a/automated-tests/src/dali-adaptor/dali-test-suite-utils/test-harness.cpp +++ b/automated-tests/src/dali-adaptor/dali-test-suite-utils/test-harness.cpp @@ -42,27 +42,23 @@ int RunTestCase( struct ::testcase_s& testCase ) { int result = EXIT_STATUS_TESTCASE_FAILED; - try +// dont want to catch exception as we want to be able to get +// gdb stack trace from the first error +// by default tests should all always pass with no exceptions + if( testCase.startup ) { - if( testCase.startup ) - { - testCase.startup(); - } - result = testCase.function(); - if( testCase.cleanup ) - { - testCase.cleanup(); - } + testCase.startup(); } - catch (...) + result = testCase.function(); + if( testCase.cleanup ) { - printf("Caught exception in test case.\n"); - result = EXIT_STATUS_TESTCASE_ABORTED; + testCase.cleanup(); } return result; } + int RunTestCaseInChildProcess( struct ::testcase_s& testCase, bool suppressOutput ) { int testResult = EXIT_STATUS_TESTCASE_FAILED; diff --git a/build/tizen/adaptor/Makefile.am b/build/tizen/adaptor/Makefile.am index beb39b2..f39980d 100644 --- a/build/tizen/adaptor/Makefile.am +++ b/build/tizen/adaptor/Makefile.am @@ -291,6 +291,7 @@ libdali_adaptor_la_LIBADD = \ $(CAPI_SYSTEM_INFO_LIBS) \ $(ELDBUS_LIBS) \ -lgif \ + -lpthread \ -lboost_thread \ -lturbojpeg -- 2.7.4