From: Wander Lairson Costa Date: Mon, 21 Sep 2020 20:03:52 +0000 (-0300) Subject: Implement a semaphore primitive X-Git-Tag: dali_2.0.3~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F32%2F244732%2F9;p=platform%2Fcore%2Fuifw%2Fdali-core.git Implement a semaphore primitive macOS doesn't implement unnamed POSIX semaphores (sem_*) and semaphores is only available in the standard C++ library starting with C++20. We implement a semaphore primitive that tries to mimic as much as possible std::counting_semaphore, this way when we enable C++20 in the code base we can easily migrate to the standard implementation. Change-Id: Ifab8dbe41b57490564ad569cf53d6aa0c4100a13 --- diff --git a/automated-tests/src/dali/CMakeLists.txt b/automated-tests/src/dali/CMakeLists.txt index bab6fb6..67aceac 100644 --- a/automated-tests/src/dali/CMakeLists.txt +++ b/automated-tests/src/dali/CMakeLists.txt @@ -47,6 +47,7 @@ SET(TC_SOURCES utc-Dali-Matrix3.cpp utc-Dali-MeshMaterial.cpp utc-Dali-Mutex.cpp + utc-Dali-Semaphore.cpp utc-Dali-ObjectRegistry.cpp utc-Dali-PanGesture.cpp utc-Dali-PanGestureDetector.cpp diff --git a/automated-tests/src/dali/dali-test-suite-utils/dali-test-suite-utils.h b/automated-tests/src/dali/dali-test-suite-utils/dali-test-suite-utils.h index 2c1c703..b8de978 100644 --- a/automated-tests/src/dali/dali-test-suite-utils/dali-test-suite-utils.h +++ b/automated-tests/src/dali/dali-test-suite-utils/dali-test-suite-utils.h @@ -378,6 +378,34 @@ inline void DALI_TEST_PRINT_ASSERT(DaliException& e) DALI_TEST_ASSERT(e, assertstring, TEST_LOCATION); \ } +/** + * Test that given piece of code triggers an exception + * Fails the test if the exception didn't occur. + * Turns off logging during the execution of the code to avoid excessive false positive log output from the assertions + * @param expressions code to execute + * @param except the exception expected in the assert + */ +#define DALI_TEST_THROWS(expressions, except) \ + try \ + { \ + TestApplication::EnableLogging(false); \ + expressions; \ + TestApplication::EnableLogging(true); \ + fprintf(stderr, "Test failed in %s, expected exception: '%s' didn't occur\n", __FILELINE__, #except); \ + tet_result(TET_FAIL); \ + throw("TET_FAIL"); \ + } \ + catch(except &) \ + { \ + tet_result(TET_PASS); \ + } \ + catch(...) \ + { \ + fprintf(stderr, "Test failed in %s, unexpected exception\n", __FILELINE__); \ + tet_result(TET_FAIL); \ + throw; \ + } + // Functor to test whether an Applied signal is emitted struct ConstraintAppliedCheck { diff --git a/automated-tests/src/dali/utc-Dali-Semaphore.cpp b/automated-tests/src/dali/utc-Dali-Semaphore.cpp new file mode 100644 index 0000000..4cda5e6 --- /dev/null +++ b/automated-tests/src/dali/utc-Dali-Semaphore.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 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 +#include +#include +#include + +int UtcDaliSemaphoreTryAcquire(void) +{ + using namespace std::chrono_literals; + constexpr auto waitTime{100ms}; + + tet_infoline("Testing Dali::Semaphore try acquire methods"); + Dali::Semaphore<3> sem(0); + + DALI_TEST_EQUALS(false, sem.TryAcquire(), TEST_LOCATION); + DALI_TEST_EQUALS(false, sem.TryAcquireFor(waitTime), TEST_LOCATION); + DALI_TEST_EQUALS(false, sem.TryAcquireUntil(std::chrono::system_clock::now() + waitTime), TEST_LOCATION); + + sem.Release(3); + + DALI_TEST_EQUALS(true, sem.TryAcquire(), TEST_LOCATION); + DALI_TEST_EQUALS(true, sem.TryAcquireFor(waitTime), TEST_LOCATION); + DALI_TEST_EQUALS(true, sem.TryAcquireUntil(std::chrono::system_clock::now() + waitTime), TEST_LOCATION); + + DALI_TEST_EQUALS(false, sem.TryAcquire(), TEST_LOCATION); + DALI_TEST_EQUALS(false, sem.TryAcquireFor(waitTime), TEST_LOCATION); + DALI_TEST_EQUALS(false, sem.TryAcquireUntil(std::chrono::system_clock::now() + waitTime), TEST_LOCATION); + + END_TEST; +} + +int UtcDaliSemaphoreInvalidArguments(void) +{ + tet_infoline("Testing Dali::Semaphore invalid arguments"); + + Dali::Semaphore<2> sem(0); + + DALI_TEST_THROWS(sem.Release(3), std::invalid_argument); + DALI_TEST_THROWS(sem.Release(-1), std::invalid_argument); + sem.Release(1); + DALI_TEST_THROWS(sem.Release(2), std::invalid_argument); + sem.Release(1); + DALI_TEST_THROWS(sem.Release(1), std::invalid_argument); + + DALI_TEST_THROWS(Dali::Semaphore<1>(2), std::invalid_argument); + DALI_TEST_THROWS(Dali::Semaphore<>(-1), std::invalid_argument); + + END_TEST; +} + +int UtcDaliSemaphoreAcquire(void) +{ + tet_infoline("Testing Dali::Semaphore multithread acquire"); + + using namespace std::chrono_literals; + + constexpr std::ptrdiff_t numTasks{2}; + + auto f = [](Dali::Semaphore &sem, bool &flag) + { + sem.Acquire(); + flag = true; + }; + + auto flag1{false}, flag2{false}; + Dali::Semaphore sem(0); + + auto fut1 = std::async(std::launch::async, f, std::ref(sem), std::ref(flag1)); + auto fut2 = std::async(std::launch::async, f, std::ref(sem), std::ref(flag2)); + + DALI_TEST_EQUALS(std::future_status::timeout, fut1.wait_for(100ms), TEST_LOCATION); + DALI_TEST_EQUALS(std::future_status::timeout, fut2.wait_for(100ms), TEST_LOCATION); + DALI_TEST_EQUALS(false, flag1, TEST_LOCATION); + DALI_TEST_EQUALS(false, flag2, TEST_LOCATION); + sem.Release(numTasks); + fut1.wait(); + DALI_TEST_EQUALS(true, flag1, TEST_LOCATION); + fut2.wait(); + DALI_TEST_EQUALS(true, flag2, TEST_LOCATION); + + END_TEST; +} diff --git a/dali/devel-api/file.list b/dali/devel-api/file.list index d65e823..1973073 100644 --- a/dali/devel-api/file.list +++ b/dali/devel-api/file.list @@ -128,6 +128,7 @@ SET( devel_api_core_scripting_header_files SET( devel_api_core_threading_header_files ${devel_api_src_dir}/threading/conditional-wait.h ${devel_api_src_dir}/threading/mutex.h + ${devel_api_src_dir}/threading/semaphore.h ${devel_api_src_dir}/threading/thread.h ${devel_api_src_dir}/threading/thread-pool.h ) diff --git a/dali/devel-api/threading/semaphore.h b/dali/devel-api/threading/semaphore.h new file mode 100644 index 0000000..9ecb41a --- /dev/null +++ b/dali/devel-api/threading/semaphore.h @@ -0,0 +1,180 @@ +#pragma once + +/* + * Copyright (c) 2020 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. + * + */ + +// INTERNAL INCLUDES +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include + +namespace Dali +{ +/** + * @brief Class that implements a C++20 counting_semaphore like interface + */ +template::max()> +class Semaphore +{ +public: + /** + * @brief Returns the internal counter's maximum possible value, + * which is greater than or equal to LeastMaxValue. + * + * @return the maximum value of the semaphore + */ + static constexpr std::ptrdiff_t Max() noexcept + { + return LeastMaxValue; + } + + /** + * @brief class constructor + * + * @param[in] desired the desired initial value of the semaphore + */ + explicit Semaphore(std::ptrdiff_t desired) + : mCount(desired) + { + if (mCount < 0 || mCount > Max()) + { + ThrowInvalidParamException(desired); + } + } + + /** + * @brief Atomically increments the internal counter by the value of update. + * + * Any thread waiting for the counter to be greater than 0 will subsequently + * be unlocked. + * + * @param[in] update value to increment the semaphore + */ + void Release(std::ptrdiff_t update = 1) + { + std::lock_guard lock(mLock); + if (update < 0 || update > Max() - mCount) + { + ThrowInvalidParamException(update); + } + + mCount += update; + while (update--) + { + mCondVar.notify_one(); + } + } + + /** + * @brief Atomically decrements the internal counter by one if it is greater + * than zero; otherwise blocks until it is greater than zero and can + * successfully decrement the internal counter. + */ + void Acquire() + { + std::unique_lock lock(mLock); + while (mCount == 0) + { + mCondVar.wait(lock); + } + --mCount; + } + + /** + * @brief Tries to atomically decrement the internal counter by one if it is + * greater than zero; no blocking occurs regardless. + * + * @return true if it decremented the counter, otherwise false. + */ + bool TryAcquire() + { + std::lock_guard lock(mLock); + if (mCount) + { + --mCount; + return true; + } + + return false; + } + + /** + * @brief Tries to atomically decrement the internal counter by one if it is greater + * than zero; otherwise blocks until it is greater than zero can successfully + * decrement the internal counter, or the relTime duration has been exceeded. + * + * @param[in] relTime the minimum duration the function must wait for to fail + * + * @return true if it decremented the internal counter, otherwise false + */ + template + bool TryAcquireFor(const std::chrono::duration &relTime) + { + std::unique_lock lock(mLock); + while (mCount == 0) + { + if (mCondVar.wait_for(lock, relTime) == std::cv_status::timeout) + { + return false; + } + } + --mCount; + return true; + } + + /** + * @brief Tries to atomically decrement the internal counter by one if it is greater + * than zero; otherwise blocks until it is greater than zero can successfully + * decrement the internal counter, or the absTime duration point has been passed. + * + * @param[in] absTime the earliest time the function must wait until in order to fail + * + * @return true if it decremented the internal counter, otherwise false + */ + template + bool TryAcquireUntil(const std::chrono::time_point &absTime) + { + std::unique_lock lock(mLock); + while (mCount == 0) + { + if (mCondVar.wait_until(lock, absTime) == std::cv_status::timeout) + { + return false; + } + } + --mCount; + return true; + } + +private: + void ThrowInvalidParamException(std::ptrdiff_t param) const + { + std::stringstream ss("Invalid parameter value "); + ss << param; + throw std::invalid_argument(ss.str()); + } + + std::condition_variable mCondVar; + std::mutex mLock; + std::ptrdiff_t mCount; +}; +}