diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..1971111 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,44 @@ +--- +name: MacOS + +on: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: Install boost libs + shell: bash + run: | + brew install boost + + - name: configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug + + - name: build + run: cmake --build build -j4 + + - name: test + run: | + cd build + ctest --build-config Debug --timeout 60 diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml new file mode 100644 index 0000000..1bae776 --- /dev/null +++ b/.github/workflows/ubuntu.yml @@ -0,0 +1,44 @@ +--- +name: Ubuntu + +on: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: Install boost libs + shell: bash + run: | + sudo apt-get install libboost1.71-all-dev + + - name: configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug + + - name: build + run: cmake --build build -j4 + + - name: test + run: | + cd build + ctest --build-config Debug --timeout 60 diff --git a/CMakeLists.txt b/CMakeLists.txt index c55eb68..0bb7d08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,10 @@ if(USE_BOOST OR BUILD_TESTING) # ---------------------------------------------------------------------- # additional tests from me (CK) # ---------------------------------------------------------------------- + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # add_compile_options(-fsanitize=memory -fsanitize-memory-use-after-dtor) + endif() + add_executable(test_mutexattr test_mutexattr.c) set_target_properties(test_mutexattr PROPERTIES CXX_STANDARD 99) target_link_libraries(test_mutexattr Threads::Threads) diff --git a/GNUmakefile b/GNUmakefile index 912c29b..8f7c8bf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -26,7 +26,7 @@ CHECKS?='-*,cppcoreguidelines-*,cppcoreguidelines-pro-*,-cppcoreguidelines-avoid CHECKS?='-*,portability-*,readability-*' CHECKS?='-*,misc-*,boost-*,cert-*,misc-unused-parameters' -#FIXME! ThreadSanitizer?=0 +#TBD: ThreadSanitizer?=0 ifeq ($(BUILD_TYPE),Coverage) ThreadSanitizer:=0 else @@ -44,6 +44,7 @@ ifeq (${ThreadSanitizer},1) export CMAKE_INSTALL_PREFIX CMAKE_STAGING_PREFIX?=/tmp/staging/$(PROJECT_NAME)$(CMAKE_INSTALL_PREFIX) CMAKE_PREFIX_PATH?="$(CMAKE_STAGING_PREFIX);$(CMAKE_INSTALL_PREFIX);/usr/local/opt/boost;/usr/local/opt/openssl;/usr" + export MSAN_OPTIONS=poison_in_dtor=1 endif diff --git a/posix/threadpool.cpp b/posix/threadpool.cpp index 53a4ecd..b199b92 100644 --- a/posix/threadpool.cpp +++ b/posix/threadpool.cpp @@ -89,45 +89,45 @@ Synchronized::Synchronized() Synchronized::~Synchronized() { - int error = 0; int errors = 0; + notify_all(); // NOTE: just in case an other thread is waiting for the + // signal! CK + int error = + pthread_mutex_unlock(&monitor); // in case the thread own the lock! CK + if (error) { + ++errors; + } + #ifdef NO_FAST_MUTEXES - do { - // first try to get the lock + error = pthread_mutex_destroy(&monitor); + if (error) { + // wait for other threads ... error = pthread_mutex_trylock(&monitor); - if (!error) { - (void)pthread_mutex_unlock(&monitor); - // if another thread waits for signal with mutex, let's wait. + if (EBUSY == error) { + ++errors; + // another thread owns the mutex, # if defined(_POSIX_TIMEOUTS) && _POSIX_TIMEOUTS > 0 - if (lock(10)) // NOTE: but not forever! CK -# else - error = pthread_mutex_trylock(&monitor); - if (!error) + if (lock(100)) { // NOTE: but not forever! CK + pthread_mutex_unlock(&monitor); + } # endif - { - (void)pthread_mutex_unlock(&monitor); - error = pthread_mutex_destroy(&monitor); - if (error) { + + int retries = 0; + do { + error = pthread_mutex_trylock(&monitor); + if (!error) { + pthread_mutex_unlock(&monitor); + error = pthread_mutex_destroy(&monitor); + } else { ++errors; - Thread::sleep(errors * 2); + sleep(errors * 2); // NOTE: prevent busy loops! CK } - } - } else { - // in case this thread hold the lock: - error = pthread_mutex_unlock(&monitor); - if (error != EPERM) { - break; - } - notify_all(); - ++errors; - Thread::sleep(errors * 2); + } while (EBUSY == error + && (retries++ < AGENTPP_SYNCHRONIZED_UNLOCK_RETRIES)); } - if (errors > AGENTPP_SYNCHRONIZED_UNLOCK_RETRIES) { - break; - } - } while (EBUSY == error); + } #else error = pthread_mutex_destroy(&monitor); #endif @@ -307,7 +307,7 @@ bool Synchronized::lock(unsigned long timeout) # else struct timeval tv = {}; gettimeofday(&tv, 0); - ts.tv_sec = tv.tv_sec + (time_t)timeout / 1000; + ts.tv_sec = tv.tv_sec + (time_t)timeout / 1000; long millis = tv.tv_usec / 1000 + (timeout % 1000); if (millis >= 1000) { ts.tv_sec += 1; diff --git a/threads.cpp b/threads.cpp index 6f1e4b9..30a722c 100644 --- a/threads.cpp +++ b/threads.cpp @@ -163,8 +163,10 @@ Synchronized::Synchronized() Synchronized::~Synchronized() { + int errors = 0; int result = pthread_cond_destroy(&cond); if (result) { + ++errors; LOG_BEGIN(loggerModuleName, ERROR_LOG | 2); LOG("Synchronized cond_destroy failed with (result)(ptr)"); LOG(result); @@ -178,12 +180,12 @@ Synchronized::~Synchronized() // wait for other threads ... if (EBUSY == pthread_mutex_trylock(&monitor)) { // another thread owns the mutex, - if (lock(123)) // NOTE: let's wait, but not forever! CK + if (lock()) // FIXME: let's wait, but not forever! CK { int retries = 0; do { pthread_mutex_unlock(&monitor); - sleep(13); + sleep(13); // NOTE: prevent busy loops! CK result = pthread_mutex_destroy(&monitor); } while (EBUSY == result && (retries++ < AGENTPP_SYNCHRONIZED_UNLOCK_RETRIES)); @@ -192,13 +194,15 @@ Synchronized::~Synchronized() } #endif - isLocked = false; if (result) { + ++errors; LOG_BEGIN(loggerModuleName, ERROR_LOG | 2); LOG("Synchronized mutex_destroy failed with (result)(ptr)"); LOG(result); LOG((unsigned long)this); LOG_END; + } else { + // NO: ThreadSanitizer: prevent data race! CK isLocked = false; } } diff --git a/threads_test.cpp b/threads_test.cpp index 7394dcd..dedc147 100644 --- a/threads_test.cpp +++ b/threads_test.cpp @@ -618,37 +618,49 @@ BOOST_AUTO_TEST_CASE(SyncDeadlock_test) BOOST_TEST(sync.unlock()); } -#ifndef _WIN32 -void handler(int signum) +BOOST_AUTO_TEST_CASE(SyncDeleteLocked_test) { - switch (signum) { - case SIGALRM: - signal(signum, SIG_DFL); - break; - default: // ignored - break; + try { + Synchronized sync; + BOOST_TEST(sync.lock()); + } catch (std::exception& e) { + BOOST_TEST_MESSAGE(BOOST_CURRENT_FUNCTION); + BOOST_TEST_MESSAGE(e.what()); } } -#endif -BOOST_AUTO_TEST_CASE(SyncDeleteLocked_test) +boost::atomic stop { false }; +void lock_task(Synchronized* m) { -#ifndef _WIN32 - signal(SIGALRM, &handler); - ualarm(1000, 0); // us -#endif + Lock l(*m); + stop = false; + + while (!stop) { + Thread::sleep(123); // NOTE: do somet work ... + m->wait(); + } + + Thread::sleep( + BOOST_THREAD_TEST_TIME_MS); // do some cleanup task ... with lock! +} +BOOST_AUTO_TEST_CASE(SyncDeleteWaiting_test) +{ Stopwatch sw; try { - auto sync = boost::make_shared(); - BOOST_TEST(sync->lock()); -#ifndef __WIN32 - sync->wait(1234); // for signal with timout -#endif + boost::thread t(lock_task, sync.get()); + t.detach(); + Thread::sleep(BOOST_THREAD_TEST_TIME_MS); BOOST_TEST_MESSAGE(BOOST_CURRENT_FUNCTION << sw.elapsed()); + { + BOOST_TEST(sync->lock()); + stop = true; // NOTE: without notify! CK + BOOST_TEST(sync->unlock()); + } + sync.reset(); // NOTE: this delete the Synchronized obj! CK } catch (std::exception& e) { BOOST_TEST_MESSAGE(BOOST_CURRENT_FUNCTION); BOOST_TEST_MESSAGE(e.what()); @@ -784,6 +796,19 @@ BOOST_AUTO_TEST_CASE(ThreadLivetime_test) BOOST_TEST_MESSAGE(BOOST_CURRENT_FUNCTION << sw.elapsed()); } +#ifndef _WIN32 +void handler(int signum) +{ + switch (signum) { + case SIGALRM: + signal(signum, SIG_DFL); + break; + default: // ignored + break; + } +} +#endif + BOOST_AUTO_TEST_CASE(ThreadNanoSleep_test) { #ifndef _WIN32