diff --git a/CMakeLists.txt b/CMakeLists.txt index d0c47554..3e957f45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,11 @@ if(BUILD_TESTING) ament_add_gmock(test_async_function_handler test/test_async_function_handler.cpp) target_link_libraries(test_async_function_handler realtime_tools thread_priority) ament_target_dependencies(test_async_function_handler lifecycle_msgs rclcpp_lifecycle) + +if(NOT WIN32) + ament_add_gmock(realtime_mutex_tests test/realtime_mutex_tests.cpp) + target_link_libraries(realtime_mutex_tests realtime_tools) +endif() endif() # Install diff --git a/include/realtime_tools/mutex.hpp b/include/realtime_tools/mutex.hpp new file mode 100644 index 00000000..f781d232 --- /dev/null +++ b/include/realtime_tools/mutex.hpp @@ -0,0 +1,201 @@ +// Copyright 2024 PAL Robotics S.L. +// +// 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. + +/// \author Sai Kishor Kothakota + +#ifndef REALTIME_TOOLS__MUTEX_HPP_ +#define REALTIME_TOOLS__MUTEX_HPP_ + +#ifdef _WIN32 +#error "The mutex.hpp header is not supported on Windows platforms" +#endif + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief A pthread mutex wrapper that provides a mutex with the priority inheritance + * protocol and a priority ceiling of 99. + * The mutex is also error checked and robust. + * This mutex is intended to be used in real-time contexts. + * @note This mutex is not recursive. + */ +namespace realtime_tools +{ +namespace detail +{ +struct error_mutex_type_t +{ + static constexpr int value = PTHREAD_MUTEX_ERRORCHECK; +}; + +struct recursive_mutex_type_t +{ + static constexpr int value = PTHREAD_MUTEX_RECURSIVE; +}; + +struct stalled_robustness_t +{ + static constexpr int value = PTHREAD_MUTEX_STALLED; +}; + +struct robust_robustness_t +{ + static constexpr int value = PTHREAD_MUTEX_ROBUST; +}; +/** + * @brief A class template that provides a pthread mutex with the priority inheritance protocol + * + * @tparam MutexType The type of the mutex. It can be one of the following: PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_RECURSIVE, PTHREAD_MUTEX_ERRORCHECK, PTHREAD_MUTEX_DEFAULT + * @tparam MutexRobustness The robustness of the mutex. It can be one of the following: PTHREAD_MUTEX_STALLED, PTHREAD_MUTEX_ROBUST + */ +template +class mutex +{ +public: + using native_handle_type = pthread_mutex_t *; + using type = MutexType; + using robustness = MutexRobustness; + + mutex() + { + pthread_mutexattr_t attr; + + const auto attr_destroy = [](pthread_mutexattr_t * mutex_attr) { + // Destroy the mutex attributes + const auto res_destroy = pthread_mutexattr_destroy(mutex_attr); + if (res_destroy != 0) { + throw std::system_error( + res_destroy, std::generic_category(), "Failed to destroy mutex attribute"); + } + }; + using attr_cleanup_t = std::unique_ptr; + auto attr_cleanup = attr_cleanup_t(&attr, attr_destroy); + + // Initialize the mutex attributes + const auto res_attr = pthread_mutexattr_init(&attr); + if (res_attr != 0) { + throw std::system_error( + res_attr, std::system_category(), "Failed to initialize mutex attribute"); + } + + // Set the mutex type to MutexType + const auto res_type = pthread_mutexattr_settype(&attr, MutexType::value); + + if (res_type != 0) { + throw std::system_error(res_type, std::system_category(), "Failed to set mutex type"); + } + + // Set the mutex attribute to use the protocol PTHREAD_PRIO_INHERIT + const auto res_protocol = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); + if (res_protocol != 0) { + throw std::system_error(res_protocol, std::system_category(), "Failed to set mutex protocol"); + } + + // Set the mutex attribute robustness to MutexRobustness + const auto res_robust = pthread_mutexattr_setrobust(&attr, MutexRobustness::value); + if (res_robust != 0) { + throw std::system_error(res_robust, std::system_category(), "Failed to set mutex robustness"); + } + + // Initialize the mutex with the attributes + const auto res_init = pthread_mutex_init(&mutex_, &attr); + if (res_init != 0) { + throw std::system_error(res_init, std::system_category(), "Failed to initialize mutex"); + } + } + + ~mutex() + { + const auto res = pthread_mutex_destroy(&mutex_); + if (res != 0) { + std::cerr << "Failed to destroy mutex : " << std::strerror(res) << std::endl; + } + } + + mutex(const mutex &) = delete; + + mutex & operator=(const mutex &) = delete; + + native_handle_type native_handle() noexcept { return &mutex_; } + + void lock() + { + const auto res = pthread_mutex_lock(&mutex_); + if (res == 0) { + return; + } + if (res == EOWNERDEAD) { + const auto res_consistent = pthread_mutex_consistent(&mutex_); + if (res_consistent != 0) { + throw std::runtime_error( + std::string("Failed to make mutex consistent : ") + std::strerror(res_consistent)); + } + std::cerr << "Mutex owner died, but the mutex is consistent now. This shouldn't happen!" + << std::endl; + } else if (res == EDEADLK) { + throw std::system_error(res, std::system_category(), "Deadlock detected"); + } else { + throw std::runtime_error(std::string("Failed to lock mutex : ") + std::strerror(res)); + } + } + + void unlock() noexcept + { + // As per the requirements of BasicLockable concept, unlock should not throw + const auto res = pthread_mutex_unlock(&mutex_); + if (res != 0) { + std::cerr << "Failed to unlock mutex : " << std::strerror(res) << std::endl; + } + } + + bool try_lock() + { + const auto res = pthread_mutex_trylock(&mutex_); + if (res == 0) { + return true; + } + if (res == EBUSY) { + return false; + } else if (res == EOWNERDEAD) { + const auto res_consistent = pthread_mutex_consistent(&mutex_); + if (res_consistent != 0) { + throw std::runtime_error( + std::string("Failed to make mutex consistent : ") + std::strerror(res_consistent)); + } + std::cerr << "Mutex owner died, but the mutex is consistent now. This shouldn't happen!" + << std::endl; + } else if (res == EDEADLK) { + throw std::system_error(res, std::system_category(), "Deadlock detected"); + } else { + throw std::runtime_error(std::string("Failed to try lock mutex : ") + std::strerror(res)); + } + return true; + } + +private: + pthread_mutex_t mutex_; +}; +} // namespace detail +using prio_inherit_mutex = detail::mutex; +using prio_inherit_recursive_mutex = + detail::mutex; +} // namespace realtime_tools + +#endif // REALTIME_TOOLS__MUTEX_HPP_ diff --git a/include/realtime_tools/realtime_helpers.hpp b/include/realtime_tools/realtime_helpers.hpp index 371f9643..ef486a49 100644 --- a/include/realtime_tools/realtime_helpers.hpp +++ b/include/realtime_tools/realtime_helpers.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -62,10 +63,37 @@ bool configure_sched_fifo(int priority); * will not swap out the pages to disk i.e., the pages are guaranteed to stay in * RAM until later unlocked - which is important for realtime applications. * \param[out] message a message describing the result of the operation - * \returns true if memory locking succeeded, false otherwise + * \returns true if memory locking succeeded, false otherwise. */ + +[[deprecated("Use std::pair lock_memory() instead.")]] bool lock_memory(std::string & message); +/** + * Locks the memory pages of the calling thread to prevent page faults. + * By calling this method, the programs locks all pages mapped into the address + * space of the calling process and future mappings. This means that the kernel + * will not swap out the pages to disk i.e., the pages are guaranteed to stay in + * RAM until later unlocked - which is important for realtime applications. + * \param[out] message a message describing the result of the operation + * \returns a pair of a boolean indicating whether the operation succeeded or not + * and a message describing the result of the operation +*/ +std::pair lock_memory(); + +/** + * Configure the caller thread affinity - Tell the scheduler to prefer a certain + * set of cores for the given thread handle. + * \note The threads created by the calling thread will inherit the affinity. + * \param[in] thread the thread handle of the thread + * \param[in] core the cpu numbers of the core. If an empty vector is passed, + * the affinity is reset to the default. + * \returns a pair of a boolean indicating whether the operation succeeded or not + * and a message describing the result of the operation +*/ +std::pair set_thread_affinity( + NATIVE_THREAD_HANDLE thread, const std::vector & cores); + /** * Configure the caller thread affinity - Tell the scheduler to prefer a certain * core for the given thread handle. @@ -101,6 +129,17 @@ std::pair set_thread_affinity(std::thread & thread, int core) */ std::pair set_current_thread_affinity(int core); +/** + * Configure the current thread affinity - Tell the scheduler to prefer a certain + * set of cores for the current thread. + * \note The threads created by the calling thread will inherit the affinity. + * \param[in] core the cpu numbers of the core. If an empty vector is passed, + * the affinity is reset to the default. + * \returns a pair of a boolean indicating whether the operation succeeded or not + * and a message describing the result of the operation +*/ +std::pair set_current_thread_affinity(const std::vector & cores); + /** * Method to get the amount of available cpu cores * \ref https://man7.org/linux/man-pages/man3/sysconf.3.html diff --git a/src/realtime_helpers.cpp b/src/realtime_helpers.cpp index a9ce1a73..e55894ce 100644 --- a/src/realtime_helpers.cpp +++ b/src/realtime_helpers.cpp @@ -65,10 +65,16 @@ bool configure_sched_fifo(int priority) } bool lock_memory(std::string & message) +{ + const auto lock_result = lock_memory(); + message = lock_result.second; + return lock_result.first; +} + +std::pair lock_memory() { #ifdef _WIN32 - message = "Memory locking is not supported on Windows."; - return false; + return {false, "Memory locking is not supported on Windows."}; #else auto is_capable = [](cap_value_t v) -> bool { bool rc = false; @@ -86,6 +92,7 @@ bool lock_memory(std::string & message) return rc; }; + std::string message; if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { if (!is_capable(CAP_IPC_LOCK)) { message = "No proper privileges to lock the memory!"; @@ -105,15 +112,16 @@ bool lock_memory(std::string & message) } else { message = "Unknown error occurred!"; } - return false; + return {false, message}; } else { message = "Memory locked successfully!"; - return true; + return {true, message}; } #endif } -std::pair set_thread_affinity(NATIVE_THREAD_HANDLE thread, int core) +std::pair set_thread_affinity( + NATIVE_THREAD_HANDLE thread, const std::vector & cores) { std::string message; #ifdef _WIN32 @@ -149,27 +157,35 @@ std::pair set_thread_affinity(NATIVE_THREAD_HANDLE thread, in // Obtain available processors const auto number_of_cores = get_number_of_available_processors(); + bool valid_cpu_set = true; // Reset affinity by setting it to all cores - if (core < 0) { + if (cores.empty()) { for (auto i = 0; i < number_of_cores; i++) { CPU_SET(i, &cpuset); } - // And actually tell the schedular to set the affinity of the thread of respective pid - const auto result = set_affinity_result_message( - pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset), message); - return std::make_pair(result, message); + } else { + for (const auto core : cores) { + if (core < 0 || core >= number_of_cores) { + valid_cpu_set = false; + break; + } + CPU_SET(core, &cpuset); + } } - if (core < number_of_cores) { - // Set the passed core to the cpu set - CPU_SET(core, &cpuset); + if (valid_cpu_set) { // And actually tell the schedular to set the affinity of the thread of respective pid const auto result = set_affinity_result_message( pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset), message); return std::make_pair(result, message); } + // create a string from the core numbers + std::string core_numbers; + for (const auto core : cores) { + core_numbers += std::to_string(core) + " "; + } // Invalid core number passed - message = "Invalid core number : '" + std::to_string(core) + "' passed! The system has " + + message = "Invalid core numbers : ['" + core_numbers + "'] passed! The system has " + std::to_string(number_of_cores) + " cores. Parsed core number should be between 0 and " + std::to_string(number_of_cores - 1); @@ -177,6 +193,12 @@ std::pair set_thread_affinity(NATIVE_THREAD_HANDLE thread, in #endif } +std::pair set_thread_affinity(NATIVE_THREAD_HANDLE thread, int core) +{ + const std::vector affinity_cores = core < 0 ? std::vector() : std::vector{core}; + return set_thread_affinity(thread, affinity_cores); +} + std::pair set_thread_affinity(std::thread & thread, int core) { if (!thread.joinable()) { @@ -195,6 +217,15 @@ std::pair set_current_thread_affinity(int core) #endif } +std::pair set_current_thread_affinity(const std::vector & cores) +{ +#ifdef _WIN32 + return set_thread_affinity(GetCurrentThread(), cores); +#else + return set_thread_affinity(pthread_self(), cores); +#endif +} + int64_t get_number_of_available_processors() { #ifdef _WIN32 diff --git a/test/realtime_mutex_tests.cpp b/test/realtime_mutex_tests.cpp new file mode 100644 index 00000000..691529c2 --- /dev/null +++ b/test/realtime_mutex_tests.cpp @@ -0,0 +1,341 @@ +// Copyright 2024 PAL Robotics S.L. +// +// 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. + +/// \author Sai Kishor Kothakota + +#include + +#include +#include + +#include + +TEST(PriorityInheritanceMutexTests, lock_unlock) +{ + // The mutex is locked and unlocked correctly + realtime_tools::prio_inherit_mutex mutex; + mutex.lock(); + mutex.unlock(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_multithreaded) +{ + // The mutex is locked and unlocked correctly in a multithreaded environment + realtime_tools::prio_inherit_mutex mutex; + std::thread t1([&mutex]() { + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex.unlock(); + }); + std::thread t2([&mutex]() { + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex.unlock(); + }); + t1.join(); + t2.join(); +} + +TEST(PriorityInheritanceMutexTests, recursive_lock_lock_unlock_multithreaded) +{ + // The mutex is locked and unlocked correctly in a multithreaded environment + realtime_tools::prio_inherit_recursive_mutex mutex; + std::thread t1([&mutex]() { + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex.unlock(); + }); + std::thread t2([&mutex]() { + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex.unlock(); + }); + t1.join(); + t2.join(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_multithreaded_multiple_mutexes) +{ + // The mutex is locked and unlocked correctly in a multithreaded environment with multiple mutexes + realtime_tools::prio_inherit_mutex mutex1; + realtime_tools::prio_inherit_mutex mutex2; + std::thread t1([&mutex1, &mutex2]() { + mutex1.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex1.unlock(); + mutex2.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex2.unlock(); + }); + std::thread t2([&mutex1, &mutex2]() { + mutex1.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex1.unlock(); + mutex2.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex2.unlock(); + }); + t1.join(); + t2.join(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_multithreaded_multiple_mutexes_different_types) +{ + // The mutex is locked and unlocked correctly in a multithreaded environment with multiple mutexes + realtime_tools::prio_inherit_mutex mutex1; + realtime_tools::prio_inherit_recursive_mutex mutex2; + std::thread t1([&mutex1, &mutex2]() { + mutex1.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex1.unlock(); + mutex2.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex2.unlock(); + }); + std::thread t2([&mutex1, &mutex2]() { + mutex1.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex1.unlock(); + mutex2.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex2.unlock(); + }); + t1.join(); + t2.join(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_recursive_mutex) +{ + // Test to check that the mutex is recursive + realtime_tools::prio_inherit_recursive_mutex mutex; + mutex.lock(); + mutex.lock(); + mutex.unlock(); + mutex.unlock(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_multithreaded_recursive_mutex_multiple_mutexes) +{ + realtime_tools::prio_inherit_recursive_mutex mutex1; + realtime_tools::prio_inherit_recursive_mutex mutex2; + std::thread t1([&mutex1, &mutex2]() { + mutex1.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex1.unlock(); + mutex2.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex2.unlock(); + }); + std::thread t2([&mutex1, &mutex2]() { + mutex1.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex1.unlock(); + mutex2.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + mutex2.unlock(); + }); + t1.join(); + t2.join(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_multithreaded_mutex_one_thread_dies) +{ + realtime_tools::prio_inherit_mutex mutex; + std::thread t1([&mutex]() { + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // simulating no unlock. Due to the robustness of the mutex, the mutex should be consistent + }); + std::thread t2([&mutex]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + mutex.lock(); + mutex.unlock(); + }); + t1.join(); + t2.join(); + mutex.lock(); + mutex.unlock(); +} + +TEST(PriorityInheritanceMutexTests, lock_unlock_multithreaded_recursive_mutex_one_thread_dies) +{ + realtime_tools::prio_inherit_recursive_mutex mutex; + std::thread t1([&mutex]() { + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // simulating no unlock. Due to the robustness of the mutex, the mutex should be consistent + }); + std::thread t2([&mutex]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + mutex.lock(); + mutex.unlock(); + }); + t1.join(); + t2.join(); + mutex.lock(); + mutex.unlock(); +} + +TEST(PriorityInheritanceMutexTests, lock_guard_mutex) +{ + realtime_tools::prio_inherit_mutex mutex; + { + std::lock_guard lock(mutex); + } + + realtime_tools::prio_inherit_recursive_mutex recursive_mutex; + { + std::lock_guard lock(recursive_mutex); + } +} + +TEST(PriorityInheritanceMutexTests, unique_lock_mutex) +{ + realtime_tools::prio_inherit_mutex mutex; + { + std::unique_lock lock(mutex); + } + + realtime_tools::prio_inherit_recursive_mutex recursive_mutex; + { + std::unique_lock lock(recursive_mutex); + } +} + +TEST(PriorityInheritanceMutexTests, try_lock_mutex) +{ + { + realtime_tools::prio_inherit_mutex mutex; + ASSERT_TRUE(mutex.try_lock()); + ASSERT_THROW(mutex.try_lock(), std::system_error) + << "Mutex is already locked in the same thread"; + std::thread t([&mutex]() { + ASSERT_FALSE(mutex.try_lock()) + << "try_lock should pass when checking from a different thread"; + }); + t.join(); + mutex.unlock(); + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); + } + + { + realtime_tools::prio_inherit_recursive_mutex recursive_mutex; + ASSERT_TRUE(recursive_mutex.try_lock()); + ASSERT_TRUE(recursive_mutex.try_lock()); + ASSERT_TRUE(recursive_mutex.try_lock()); + recursive_mutex.unlock(); + recursive_mutex.unlock(); + recursive_mutex.unlock(); + } +} + +TEST(PriorityInheritanceMutexTests, standard_lock_test) +{ + realtime_tools::prio_inherit_mutex mutex1; + realtime_tools::prio_inherit_mutex mutex2; + { + std::lock(mutex1, mutex2); + // do work + mutex1.unlock(); + mutex2.unlock(); + } + { + std::scoped_lock lock(mutex1, mutex2); + } + ASSERT_TRUE(mutex1.try_lock()); + ASSERT_TRUE(mutex2.try_lock()); + mutex1.unlock(); + mutex2.unlock(); +} + +TEST(PriorityInheritanceMutexTests, native_handle_mutex) +{ + { + realtime_tools::prio_inherit_mutex mutex; + auto native_handle = mutex.native_handle(); + ASSERT_NE(native_handle, nullptr); + } + + { + realtime_tools::prio_inherit_recursive_mutex recursive_mutex; + auto native_handle = recursive_mutex.native_handle(); + ASSERT_NE(native_handle, nullptr); + } +} + +TEST(PriorityInheritanceMutexTests, test_mutex_lock_functionality) +{ + // Trying to lock again should throw an exception + realtime_tools::prio_inherit_mutex mutex; + mutex.lock(); + ASSERT_THROW(mutex.lock(), std::system_error); + mutex.unlock(); + ASSERT_NO_THROW(mutex.lock()); + ASSERT_THROW(mutex.try_lock(), std::system_error); + mutex.unlock(); + ASSERT_NO_THROW(mutex.try_lock()); + mutex.unlock(); +} + +TEST(PriorityInheritanceMutexTests, test_lock_constructors) +{ + realtime_tools::prio_inherit_mutex mutex; + { + std::unique_lock lock(mutex, std::defer_lock); + ASSERT_FALSE(lock.owns_lock()); + lock.lock(); + ASSERT_TRUE(lock.owns_lock()); + lock.unlock(); + } + { + std::unique_lock lock(mutex, std::try_to_lock); + ASSERT_TRUE(lock.owns_lock()); + } +} + +TEST(PriorityInheritanceMutexTests, test_deadlock_detection) +{ + realtime_tools::prio_inherit_mutex mutex; + mutex.lock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ASSERT_THROW(mutex.try_lock(), std::system_error); + ASSERT_THROW(mutex.lock(), std::system_error); + // In a different thread, try to lock the mutex should not throw an exception + std::thread t([&mutex]() { ASSERT_FALSE(mutex.try_lock()); }); + t.join(); + mutex.unlock(); +} + +TEST(PriorityInheritanceMutexTests, test_mutex_reflection) +{ + static_assert( + std::is_same< + realtime_tools::prio_inherit_mutex::type, + realtime_tools::detail::error_mutex_type_t>::value == true); + static_assert( + std::is_same< + realtime_tools::prio_inherit_recursive_mutex::type, + realtime_tools::detail::recursive_mutex_type_t>::value == true); +} + +int main(int argc, char ** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/thread_priority_tests.cpp b/test/thread_priority_tests.cpp index fc552cdd..e5318b8e 100644 --- a/test/thread_priority_tests.cpp +++ b/test/thread_priority_tests.cpp @@ -56,6 +56,11 @@ TEST(thread_priority, set_thread_affinity_invalid_too_many_cores) const int count = static_cast(realtime_tools::get_number_of_available_processors()); // We should always have at least one core EXPECT_FALSE(realtime_tools::set_thread_affinity(t.native_handle(), count + 10).first); + EXPECT_FALSE(realtime_tools::set_thread_affinity(t.native_handle(), {0, 1, count + 2}).first); + EXPECT_FALSE(realtime_tools::set_thread_affinity(t.native_handle(), {0, -1, 2}).first); + EXPECT_FALSE(realtime_tools::set_thread_affinity(t.native_handle(), {0, 3, -1}).first); + EXPECT_FALSE( + realtime_tools::set_thread_affinity(t.native_handle(), std::vector({-1})).first); t.join(); } @@ -64,6 +69,10 @@ TEST(thread_priority, set_current_thread_affinity_invalid_too_many_cores) const int count = static_cast(realtime_tools::get_number_of_available_processors()); // We should always have at least one core EXPECT_FALSE(realtime_tools::set_current_thread_affinity(count + 10).first); + EXPECT_FALSE(realtime_tools::set_current_thread_affinity({count + 10}).first); + EXPECT_FALSE(realtime_tools::set_current_thread_affinity({0, count + 10}).first); + EXPECT_FALSE(realtime_tools::set_current_thread_affinity({0, -1}).first); + EXPECT_FALSE(realtime_tools::set_current_thread_affinity(std::vector({-1})).first); } TEST(thread_priority, set_thread_affinity_valid_reset) @@ -72,6 +81,7 @@ TEST(thread_priority, set_thread_affinity_valid_reset) std::thread t([]() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); }); // Reset core affinity EXPECT_TRUE(realtime_tools::set_thread_affinity(t.native_handle(), -1).first); + EXPECT_TRUE(realtime_tools::set_thread_affinity(t.native_handle(), {}).first); t.join(); } @@ -79,10 +89,13 @@ TEST(thread_priority, set_current_thread_affinity_valid_reset) { // Reset core affinity EXPECT_TRUE(realtime_tools::set_current_thread_affinity(-1).first); + EXPECT_TRUE(realtime_tools::set_current_thread_affinity({}).first); } TEST(thread_priority, set_current_thread_affinity_valid) { // We should always have at least one core EXPECT_TRUE(realtime_tools::set_current_thread_affinity(0).first); + const int count = static_cast(realtime_tools::get_number_of_available_processors()); + EXPECT_TRUE(realtime_tools::set_current_thread_affinity({0, count - 1}).first); }