From 7a78598c47de88e489c977c2ded6cdb01d39b055 Mon Sep 17 00:00:00 2001 From: Miguel Company Date: Thu, 27 May 2021 16:37:53 +0200 Subject: [PATCH] WaitSet detail classes (#1989) * Refs 11608. Required changes on Condition API. Signed-off-by: Miguel Company * Refs 11608. ConditionNotifier with empty implementation. Signed-off-by: Miguel Company * Refs 11608. WaitSetImpl with empty implementation. Signed-off-by: Miguel Company * Refs 11608. ConditionNotifier unit test. Signed-off-by: Miguel Company * Refs 11608. WaitSetImpl unit test. Signed-off-by: Miguel Company * Refs 11608. Added unordered_vector. Signed-off-by: Miguel Company * Refs 11608. ConditionNotifier implementation. Signed-off-by: Miguel Company * Refs 11608. Condition management on WaitSetImpl. Signed-off-by: Miguel Company * Refs 11608. Implenting wait and wake_up on WaitSetImpl. Signed-off-by: Miguel Company * Refs 11656. Linters. Signed-off-by: Miguel Company * Refs 11656. Solving link issues. Signed-off-by: Miguel Company * Refs 11656. Doxygen improvements. Signed-off-by: Miguel Company * Refs 11656. Check vector size on test. Signed-off-by: Miguel Company --- .../fastdds/dds/core/condition/Condition.hpp | 4 +- .../dds/core/condition/GuardCondition.hpp | 2 - src/cpp/CMakeLists.txt | 2 + .../core/condition/ConditionNotifier.cpp | 75 ++++++++ .../core/condition/ConditionNotifier.hpp | 78 ++++++++ .../fastdds/core/condition/WaitSetImpl.cpp | 130 +++++++++++++ .../fastdds/core/condition/WaitSetImpl.hpp | 107 +++++++++++ .../utils/collections/unordered_vector.hpp | 38 ++++ .../dds/core/condition/CMakeLists.txt | 56 +++++- .../core/condition/ConditionNotifierTests.cpp | 93 ++++++++++ .../fastdds/core/condition/WaitSetImpl.hpp | 50 +++++ .../dds/core/condition/WaitSetImplTests.cpp | 173 ++++++++++++++++++ .../core/condition/ConditionNotifier.hpp | 67 +++++++ 13 files changed, 867 insertions(+), 8 deletions(-) create mode 100644 src/cpp/fastdds/core/condition/ConditionNotifier.cpp create mode 100644 src/cpp/fastdds/core/condition/ConditionNotifier.hpp create mode 100644 src/cpp/fastdds/core/condition/WaitSetImpl.cpp create mode 100644 src/cpp/fastdds/core/condition/WaitSetImpl.hpp create mode 100644 src/cpp/utils/collections/unordered_vector.hpp create mode 100644 test/unittest/dds/core/condition/ConditionNotifierTests.cpp create mode 100644 test/unittest/dds/core/condition/ConditionNotifierTests/fastdds/core/condition/WaitSetImpl.hpp create mode 100644 test/unittest/dds/core/condition/WaitSetImplTests.cpp create mode 100644 test/unittest/dds/core/condition/WaitSetImplTests/fastdds/core/condition/ConditionNotifier.hpp diff --git a/include/fastdds/dds/core/condition/Condition.hpp b/include/fastdds/dds/core/condition/Condition.hpp index 552cc0ee0a0..8178cd883be 100644 --- a/include/fastdds/dds/core/condition/Condition.hpp +++ b/include/fastdds/dds/core/condition/Condition.hpp @@ -41,7 +41,7 @@ class Condition * @brief Retrieves the trigger_value of the Condition * @return true if trigger_value is set to 'true', 'false' otherwise */ - RTPS_DllAPI bool get_trigger_value() const + RTPS_DllAPI virtual bool get_trigger_value() const { logWarning(CONDITION, "get_trigger_value public member function not implemented"); return false; // TODO return trigger value @@ -49,7 +49,7 @@ class Condition }; -typedef std::vector ConditionSeq; +using ConditionSeq = std::vector; } // namespace dds } // namespace fastdds diff --git a/include/fastdds/dds/core/condition/GuardCondition.hpp b/include/fastdds/dds/core/condition/GuardCondition.hpp index 1f6aed02465..d107825b2db 100644 --- a/include/fastdds/dds/core/condition/GuardCondition.hpp +++ b/include/fastdds/dds/core/condition/GuardCondition.hpp @@ -59,8 +59,6 @@ class GuardCondition : public Condition }; -typedef std::vector ConditionSeq; - } // namespace dds } // namespace fastdds } // namespace eprosima diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt index 04d313bf26e..bd585408dd8 100644 --- a/src/cpp/CMakeLists.txt +++ b/src/cpp/CMakeLists.txt @@ -158,8 +158,10 @@ set(${PROJECT_NAME}_source_files dynamic-types/DynamicDataHelper.cpp fastrtps_deprecated/attributes/TopicAttributes.cpp + fastdds/core/condition/ConditionNotifier.cpp fastdds/core/condition/StatusCondition.cpp fastdds/core/condition/WaitSet.cpp + fastdds/core/condition/WaitSetImpl.cpp fastdds/core/policy/ParameterList.cpp fastdds/core/policy/QosPolicyUtils.cpp fastdds/publisher/qos/WriterQos.cpp diff --git a/src/cpp/fastdds/core/condition/ConditionNotifier.cpp b/src/cpp/fastdds/core/condition/ConditionNotifier.cpp new file mode 100644 index 00000000000..0b7159d539f --- /dev/null +++ b/src/cpp/fastdds/core/condition/ConditionNotifier.cpp @@ -0,0 +1,75 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file ConditionNotifier.cpp + */ + +#include "ConditionNotifier.hpp" + +#include + +#include + +#include + +namespace eprosima { +namespace fastdds { +namespace dds { +namespace detail { + +void ConditionNotifier::attach_to ( + WaitSetImpl* wait_set) +{ + if (nullptr != wait_set) + { + std::lock_guard guard(mutex_); + entries_.remove(wait_set); + entries_.emplace_back(wait_set); + } +} + +void ConditionNotifier::detach_from ( + WaitSetImpl* wait_set) +{ + if (nullptr != wait_set) + { + std::lock_guard guard(mutex_); + entries_.remove(wait_set); + } +} + +void ConditionNotifier::notify () +{ + std::lock_guard guard(mutex_); + for (WaitSetImpl* wait_set : entries_) + { + wait_set->wake_up(); + } +} + +void ConditionNotifier::will_be_deleted ( + const Condition& condition) +{ + std::lock_guard guard(mutex_); + for (WaitSetImpl* wait_set : entries_) + { + wait_set->will_be_deleted(condition); + } +} + +} // namespace detail +} // namespace dds +} // namespace fastdds +} // namespace eprosima diff --git a/src/cpp/fastdds/core/condition/ConditionNotifier.hpp b/src/cpp/fastdds/core/condition/ConditionNotifier.hpp new file mode 100644 index 00000000000..f188bd8787a --- /dev/null +++ b/src/cpp/fastdds/core/condition/ConditionNotifier.hpp @@ -0,0 +1,78 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file ConditionNotifier.hpp + */ + +#ifndef _FASTDDS_CORE_CONDITION_CONDITIONNOTIFIER_HPP_ +#define _FASTDDS_CORE_CONDITION_CONDITIONNOTIFIER_HPP_ + +#include + +#include + +#include + +namespace eprosima { +namespace fastdds { +namespace dds { +namespace detail { + +struct WaitSetImpl; + +struct ConditionNotifier +{ + /** + * Add a WaitSet implementation to the list of attached entries. + * Does nothing if wait_set was already attached to this notifier. + * @param wait_set WaitSet implementation to add to the list. + */ + void attach_to ( + WaitSetImpl* wait_set); + + + /** + * Remove a WaitSet implementation from the list of attached entries. + * Does nothing if wait_set was not attached to this notifier. + * @param wait_set WaitSet implementation to remove from the list. + */ + void detach_from ( + WaitSetImpl* wait_set); + + /** + * Wake up all the WaitSet implementations attached to this notifier. + */ + void notify (); + + /** + * Inform all the WaitSet implementations attached to this notifier that + * a condition is going to be deleted. + * @param condition The Condition being deleted. + */ + void will_be_deleted ( + const Condition& condition); + +private: + + std::mutex mutex_; + eprosima::utilities::collections::unordered_vector entries_; +}; + +} // namespace detail +} // namespace dds +} // namespace fastdds +} // namespace eprosima + +#endif // _FASTDDS_CORE_CONDITION_CONDITIONNOTIFIER_HPP_ diff --git a/src/cpp/fastdds/core/condition/WaitSetImpl.cpp b/src/cpp/fastdds/core/condition/WaitSetImpl.cpp new file mode 100644 index 00000000000..d42d51ad332 --- /dev/null +++ b/src/cpp/fastdds/core/condition/WaitSetImpl.cpp @@ -0,0 +1,130 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file WaitSetImpl.cpp + */ + +#include "WaitSetImpl.hpp" + +#include +#include + +#include +#include +#include + +using eprosima::fastrtps::types::ReturnCode_t; + +namespace eprosima { +namespace fastdds { +namespace dds { +namespace detail { + +ReturnCode_t WaitSetImpl::attach_condition( + const Condition& condition) +{ + std::lock_guard guard(mutex_); + bool was_there = entries_.remove(&condition); + entries_.emplace_back(&condition); + + // Should wake_up when adding a new triggered condition + if (is_waiting_ && !was_there && condition.get_trigger_value()) + { + wake_up(); + } + + return ReturnCode_t::RETCODE_OK; +} + +ReturnCode_t WaitSetImpl::detach_condition( + const Condition& condition) +{ + std::lock_guard guard(mutex_); + bool was_there = entries_.remove(&condition); + return was_there ? ReturnCode_t::RETCODE_OK : ReturnCode_t::RETCODE_PRECONDITION_NOT_MET; +} + +ReturnCode_t WaitSetImpl::wait( + ConditionSeq& active_conditions, + const fastrtps::Duration_t& timeout) +{ + std::unique_lock lock(mutex_); + + if (is_waiting_) + { + return ReturnCode_t::RETCODE_PRECONDITION_NOT_MET; + } + + auto fill_active_conditions = [&]() + { + bool ret_val = entries_.empty(); + active_conditions.clear(); + for (const Condition* c : entries_) + { + if (c->get_trigger_value()) + { + ret_val = true; + active_conditions.push_back(const_cast(c)); + } + } + return ret_val; + }; + + bool condition_value = false; + is_waiting_ = true; + if (fastrtps::c_TimeInfinite == timeout) + { + cond_.wait(lock, fill_active_conditions); + condition_value = true; + } + else + { + auto ns = timeout.to_ns(); + condition_value = cond_.wait_for(lock, std::chrono::nanoseconds(ns), fill_active_conditions); + } + is_waiting_ = false; + + return condition_value ? ReturnCode_t::RETCODE_OK : ReturnCode_t::RETCODE_TIMEOUT; +} + +ReturnCode_t WaitSetImpl::get_conditions( + ConditionSeq& attached_conditions) const +{ + std::lock_guard guard(mutex_); + attached_conditions.reserve(entries_.size()); + attached_conditions.clear(); + for (const Condition* c : entries_) + { + attached_conditions.push_back(const_cast(c)); + } + return ReturnCode_t::RETCODE_OK; +} + +void WaitSetImpl::wake_up() +{ + cond_.notify_one(); +} + +void WaitSetImpl::will_be_deleted ( + const Condition& condition) +{ + std::lock_guard guard(mutex_); + entries_.remove(&condition); +} + +} // namespace detail +} // namespace dds +} // namespace fastdds +} // namespace eprosima diff --git a/src/cpp/fastdds/core/condition/WaitSetImpl.hpp b/src/cpp/fastdds/core/condition/WaitSetImpl.hpp new file mode 100644 index 00000000000..b043dbb4b00 --- /dev/null +++ b/src/cpp/fastdds/core/condition/WaitSetImpl.hpp @@ -0,0 +1,107 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file WaitSetImpl.hpp + */ + +#ifndef _FASTDDS_CORE_CONDITION_WAITSETIMPL_HPP_ +#define _FASTDDS_CORE_CONDITION_WAITSETIMPL_HPP_ + +#include +#include + +#include +#include +#include +#include + +using eprosima::fastrtps::types::ReturnCode_t; + +namespace eprosima { +namespace fastdds { +namespace dds { +namespace detail { + +struct WaitSetImpl +{ + /** + * @brief Attach a Condition to this WaitSet implementation + * @param condition The Condition to attach to this WaitSet implementation + * @return RETCODE_OK + */ + ReturnCode_t attach_condition( + const Condition& condition); + + /** + * @brief Detach a Condition from this WaitSet implementation + * @param condition The Condition to detach from this WaitSet implementation + * @return RETCODE_OK if detached correctly + * @return PRECONDITION_NOT_MET if condition was not attached + */ + ReturnCode_t detach_condition( + const Condition& condition); + + /** + * @brief Wait for any of the attached conditions to be triggered. + * If none of the conditions attached to this WaitSet implementation have a trigger_value of true, + * this operation will block, suspending the calling thread. + * The list of conditions with a trigger_value of true will be returned on active_conditions. + * It is not possible to call this operation from two different threads at the same time (PRECONDITION_NOT_MET + * will be returned in that case) + * + * @param active_conditions Reference to the collection of conditions that have a trigger_value of true + * @param timeout Maximum time of the wait + * @return RETCODE_OK if everything correct + * @return PRECONDITION_NOT_MET if WaitSet already waiting + * @return TIMEOUT if maximum time expired + */ + ReturnCode_t wait( + ConditionSeq& active_conditions, + const fastrtps::Duration_t& timeout); + + /** + * @brief Retrieve the list of attached conditions + * @param attached_conditions Reference to the collection of attached conditions + * @return RETCODE_OK + */ + ReturnCode_t get_conditions( + ConditionSeq& attached_conditions) const; + + /** + * @brief Wake up this WaitSet implementation if it was waiting + */ + void wake_up(); + + /** + * @brief Called from the destructor of a Condition to inform this WaitSet implementation that the condition + * should be automatically detached. + */ + void will_be_deleted ( + const Condition& condition); + +private: + + mutable std::mutex mutex_; + std::condition_variable cond_; + eprosima::utilities::collections::unordered_vector entries_; + bool is_waiting_ = false; +}; + +} // namespace detail +} // namespace dds +} // namespace fastdds +} // namespace eprosima + +#endif // _FASTDDS_CORE_CONDITION_WAITSETIMPL_HPP_ diff --git a/src/cpp/utils/collections/unordered_vector.hpp b/src/cpp/utils/collections/unordered_vector.hpp new file mode 100644 index 00000000000..f9d96d88a58 --- /dev/null +++ b/src/cpp/utils/collections/unordered_vector.hpp @@ -0,0 +1,38 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file unordered_vector.hpp + */ + +#ifndef SRC_CPP_UTILS_COLLECTIONS_UNORDERED_VECTOR_HPP_ +#define SRC_CPP_UTILS_COLLECTIONS_UNORDERED_VECTOR_HPP_ + +#include + +namespace eprosima { +namespace utilities { +namespace collections { + +template < + typename _Ty, + typename _Alloc = std::allocator<_Ty>> +using unordered_vector = eprosima::fastrtps::ResourceLimitedVector< + _Ty, std::false_type, eprosima::fastrtps::ResourceLimitedContainerConfig, _Alloc>; + +} // namespace collections +} // namespace utilities +} // namespace eprosima + +#endif /* SRC_CPP_UTILS_COLLECTIONS_NODE_SIZE_HELPERS_HPP_ */ diff --git a/test/unittest/dds/core/condition/CMakeLists.txt b/test/unittest/dds/core/condition/CMakeLists.txt index 80301017f9c..7ead544f120 100644 --- a/test/unittest/dds/core/condition/CMakeLists.txt +++ b/test/unittest/dds/core/condition/CMakeLists.txt @@ -15,6 +15,14 @@ if(NOT ((MSVC OR MSVC_IDE) AND EPROSIMA_INSTALLER)) include(${PROJECT_SOURCE_DIR}/cmake/common/gtest.cmake) check_gtest() + check_gmock() + + set(LOG_SOURCES + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/Log.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/OStreamConsumer.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/StdoutConsumer.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/StdoutErrConsumer.cpp + ) if(GTEST_FOUND) if(WIN32) @@ -25,11 +33,8 @@ if(NOT ((MSVC OR MSVC_IDE) AND EPROSIMA_INSTALLER)) ${PROJECT_SOURCE_DIR}/src/cpp/dynamic-types/TypesBase.cpp ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/condition/StatusCondition.cpp ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/condition/WaitSet.cpp - ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/Log.cpp - ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/OStreamConsumer.cpp - ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/StdoutConsumer.cpp - ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/StdoutErrConsumer.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/Time_t.cpp + ${LOG_SOURCES} ConditionTests.cpp) add_executable(ConditionTests ${CONDITION_TESTS_SOURCE}) @@ -42,4 +47,47 @@ if(NOT ((MSVC OR MSVC_IDE) AND EPROSIMA_INSTALLER)) target_link_libraries(ConditionTests ${GTEST_LIBRARIES} fastcdr) add_gtest(ConditionTests SOURCES ${CONDITION_TESTS_SOURCE}) endif() + + if(GMOCK_FOUND) + set(CONDITION_NOTIFIER_TESTS_SOURCE + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/condition/ConditionNotifier.cpp + ${LOG_SOURCES} + ConditionNotifierTests.cpp) + + add_executable(ConditionNotifierTests ${CONDITION_NOTIFIER_TESTS_SOURCE}) + target_compile_definitions(ConditionNotifierTests PRIVATE FASTRTPS_NO_LIB + $<$>,$>:__DEBUG> + $<$:__INTERNALDEBUG> # Internal debug activated. + ) + target_include_directories(ConditionNotifierTests PRIVATE + ConditionNotifierTests + ${GTEST_INCLUDE_DIRS} + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${PROJECT_SOURCE_DIR}/src/cpp + ) + target_link_libraries(ConditionNotifierTests ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) + add_gtest(ConditionNotifierTests SOURCES ${CONDITION_NOTIFIER_TESTS_SOURCE}) + + set(WAITSET_IMPL_TESTS_SOURCE + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/condition/WaitSetImpl.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/Time_t.cpp + ${LOG_SOURCES} + WaitSetImplTests.cpp) + + add_executable(WaitSetImplTests ${WAITSET_IMPL_TESTS_SOURCE}) + target_compile_definitions(WaitSetImplTests PRIVATE FASTRTPS_NO_LIB + $<$>,$>:__DEBUG> + $<$:__INTERNALDEBUG> # Internal debug activated. + ) + target_include_directories(WaitSetImplTests PRIVATE + WaitSetImplTests + ${GTEST_INCLUDE_DIRS} + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${PROJECT_SOURCE_DIR}/src/cpp + ) + target_link_libraries(WaitSetImplTests ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) + add_gtest(WaitSetImplTests SOURCES ${WAITSET_IMPL_TESTS_SOURCE}) + endif() endif() diff --git a/test/unittest/dds/core/condition/ConditionNotifierTests.cpp b/test/unittest/dds/core/condition/ConditionNotifierTests.cpp new file mode 100644 index 00000000000..029f364b714 --- /dev/null +++ b/test/unittest/dds/core/condition/ConditionNotifierTests.cpp @@ -0,0 +1,93 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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 mocks first +#include + +// Include UUT +#include + +// Other includes +#include + +using namespace eprosima::fastdds::dds; +using namespace eprosima::fastdds::dds::detail; + +TEST(ConditionNotifierTests, basic_test) +{ + WaitSetImpl wait_set; + ConditionNotifier notifier; + Condition condition; + + auto test_steps = [&]() + { + // This should not call wake_up, as the wait_set has not been attached to the notifier (ncalls = 0/1) + notifier.notify(); + notifier.will_be_deleted(condition); + + // Waitset should be called after being attached (ncalls = 1/2) + notifier.attach_to(&wait_set); + notifier.notify(); + notifier.will_be_deleted(condition); + + // Attaching nullptr should not fail and the other waitset should be called (ncalls = 2/3) + notifier.attach_to(nullptr); + notifier.notify(); + notifier.will_be_deleted(condition); + + // Attaching same waitset should not duplicate calls (ncalls = 3/4) + notifier.attach_to(&wait_set); + notifier.notify(); + notifier.will_be_deleted(condition); + + // Detaching nullptr should not fail and the other waitset should still be called (ncalls = 4/5) + notifier.detach_from(nullptr); + notifier.notify(); + notifier.will_be_deleted(condition); + + // Waitset should not be called after being detached (ncalls = 4/6) + notifier.detach_from(&wait_set); + notifier.notify(); + notifier.will_be_deleted(condition); + + // Waitset is allowed to be removed twice (ncalls = 4/7) + notifier.detach_from(&wait_set); + notifier.notify(); + notifier.will_be_deleted(condition); + }; + + EXPECT_CALL(wait_set, wake_up).Times(4); + EXPECT_CALL(wait_set, will_be_deleted).Times(4); + test_steps(); + testing::Mock::VerifyAndClearExpectations(&wait_set); + + WaitSetImpl other_waitset; + notifier.attach_to(&other_waitset); + + EXPECT_CALL(wait_set, wake_up).Times(4); + EXPECT_CALL(wait_set, will_be_deleted).Times(4); + EXPECT_CALL(other_waitset, wake_up).Times(7); + EXPECT_CALL(other_waitset, will_be_deleted).Times(7); + test_steps(); +} + +int main( + int argc, + char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/unittest/dds/core/condition/ConditionNotifierTests/fastdds/core/condition/WaitSetImpl.hpp b/test/unittest/dds/core/condition/ConditionNotifierTests/fastdds/core/condition/WaitSetImpl.hpp new file mode 100644 index 00000000000..d7ee0f82125 --- /dev/null +++ b/test/unittest/dds/core/condition/ConditionNotifierTests/fastdds/core/condition/WaitSetImpl.hpp @@ -0,0 +1,50 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file WaitSetImpl.hpp + */ + +#ifndef _FASTDDS_CORE_CONDITION_WAITSETIMPL_HPP_ +#define _FASTDDS_CORE_CONDITION_WAITSETIMPL_HPP_ + +#include + +#include + +namespace eprosima { +namespace fastdds { +namespace dds { +namespace detail { + +struct WaitSetImpl +{ + /** + * @brief Wake up this WaitSet implementation if it was waiting + */ + MOCK_METHOD0(wake_up, void()); + + /** + * @brief Called from the destructor of a Condition to inform this WaitSet implementation that the condition + * should be automatically detached. + */ + MOCK_METHOD1(will_be_deleted, void(const Condition& condition)); +}; + +} // namespace detail +} // namespace dds +} // namespace fastdds +} // namespace eprosima + +#endif // _FASTDDS_CORE_CONDITION_WAITSETIMPL_HPP_ diff --git a/test/unittest/dds/core/condition/WaitSetImplTests.cpp b/test/unittest/dds/core/condition/WaitSetImplTests.cpp new file mode 100644 index 00000000000..5b7a53efce1 --- /dev/null +++ b/test/unittest/dds/core/condition/WaitSetImplTests.cpp @@ -0,0 +1,173 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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 mocks first +#include + +// Include UUT +#include + +// Other includes +#include + +using namespace eprosima::fastdds::dds; +using namespace eprosima::fastdds::dds::detail; + +TEST(WaitSetImplTests, condition_management) +{ + Condition condition; + ConditionSeq conditions; + WaitSetImpl wait_set; + + // WaitSetImpl should be created without conditions + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.get_conditions(conditions)); + EXPECT_TRUE(conditions.empty()); + + // Trying to detach without having attached + EXPECT_EQ(ReturnCode_t::RETCODE_PRECONDITION_NOT_MET, wait_set.detach_condition(condition)); + + // Adding the same condition several times should always succeed and keep the list with a single condition + for (int i = 0; i < 2; ++i) + { + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.attach_condition(condition)); + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.get_conditions(conditions)); + EXPECT_EQ(1u, conditions.size()); + EXPECT_NE(conditions.cend(), std::find(conditions.cbegin(), conditions.cend(), &condition)); + } + + // Detaching the condition once should succeed + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.detach_condition(condition)); + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.get_conditions(conditions)); + EXPECT_TRUE(conditions.empty()); + + // Detaching a second time should fail + EXPECT_EQ(ReturnCode_t::RETCODE_PRECONDITION_NOT_MET, wait_set.detach_condition(condition)); + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.get_conditions(conditions)); + EXPECT_TRUE(conditions.empty()); + + // Attach the condition again + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.attach_condition(condition)); + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.get_conditions(conditions)); + EXPECT_EQ(1u, conditions.size()); + EXPECT_NE(conditions.cend(), std::find(conditions.cbegin(), conditions.cend(), &condition)); + + // Calling will_be_deleted should detach the condition + wait_set.will_be_deleted(condition); + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.get_conditions(conditions)); + EXPECT_TRUE(conditions.empty()); +} + +TEST(WaitSetImplTests, wait) +{ + class TestCondition : public Condition + { + public: + + bool trigger_value = false; + + bool get_trigger_value() const override + { + return trigger_value; + } + + }; + + TestCondition condition; + ConditionSeq conditions; + WaitSetImpl wait_set; + const eprosima::fastrtps::Duration_t timeout{ 1, 0 }; + + // Waiting on empty wait set should inmediately return OK + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.wait(conditions, timeout)); + EXPECT_TRUE(conditions.empty()); + + // Attach condition + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.attach_condition(condition)); + + // Waiting on untriggered condition should timeout + EXPECT_EQ(ReturnCode_t::RETCODE_TIMEOUT, wait_set.wait(conditions, timeout)); + EXPECT_TRUE(conditions.empty()); + + // Waiting on already triggered condition should inmediately return condition + condition.trigger_value = true; + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.wait(conditions, timeout)); + EXPECT_EQ(1u, conditions.size()); + EXPECT_NE(conditions.cend(), std::find(conditions.cbegin(), conditions.cend(), &condition)); + + // A wake_up without a trigger should timeout + condition.trigger_value = false; + std::thread notify_without_trigger([&]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + wait_set.wake_up(); + }); + EXPECT_EQ(ReturnCode_t::RETCODE_TIMEOUT, wait_set.wait(conditions, timeout)); + EXPECT_TRUE(conditions.empty()); + + // A wake_up with a trigger should return the condition + std::thread trigger_and_notify([&]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + condition.trigger_value = true; + wait_set.wake_up(); + }); + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.wait(conditions, timeout)); + EXPECT_EQ(1u, conditions.size()); + EXPECT_NE(conditions.cend(), std::find(conditions.cbegin(), conditions.cend(), &condition)); + + // Two threads are not allowed to wait at the same time + std::thread second_wait_thread([&wait_set, &timeout]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + ConditionSeq conds; + EXPECT_EQ(ReturnCode_t::RETCODE_PRECONDITION_NOT_MET, wait_set.wait(conds, timeout)); + EXPECT_TRUE(conds.empty()); + }); + + condition.trigger_value = false; + EXPECT_EQ(ReturnCode_t::RETCODE_TIMEOUT, wait_set.wait(conditions, timeout)); + EXPECT_TRUE(conditions.empty()); + + // Waiting forever and adding a triggered condition should wake and only return the added condition + TestCondition triggered_condition; + triggered_condition.trigger_value = true; + + std::thread add_triggered_condition([&]() + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + wait_set.attach_condition(triggered_condition); + }); + + EXPECT_EQ(ReturnCode_t::RETCODE_OK, wait_set.wait(conditions, eprosima::fastrtps::c_TimeInfinite)); + EXPECT_EQ(1u, conditions.size()); + EXPECT_EQ(conditions.cend(), std::find(conditions.cbegin(), conditions.cend(), &condition)); + EXPECT_NE(conditions.cend(), std::find(conditions.cbegin(), conditions.cend(), &triggered_condition)); + + notify_without_trigger.join(); + trigger_and_notify.join(); + second_wait_thread.join(); + add_triggered_condition.join(); +} + +int main( + int argc, + char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/unittest/dds/core/condition/WaitSetImplTests/fastdds/core/condition/ConditionNotifier.hpp b/test/unittest/dds/core/condition/WaitSetImplTests/fastdds/core/condition/ConditionNotifier.hpp new file mode 100644 index 00000000000..0ef854e6e59 --- /dev/null +++ b/test/unittest/dds/core/condition/WaitSetImplTests/fastdds/core/condition/ConditionNotifier.hpp @@ -0,0 +1,67 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file ConditionNotifier.hpp + */ + +#ifndef _FASTDDS_CORE_CONDITION_CONDITIONNOTIFIER_HPP_ +#define _FASTDDS_CORE_CONDITION_CONDITIONNOTIFIER_HPP_ + +#include + +#include + +namespace eprosima { +namespace fastdds { +namespace dds { +namespace detail { + +struct WaitSetImpl; + +struct ConditionNotifier +{ + /** + * Add a WaitSet implementation to the list of attached entries. + * Does nothing if wait_set was already attached to this notifier. + * @param wait_set WaitSet implementation to add to the list. + */ + MOCK_METHOD1(attach_to, void(WaitSetImpl * wait_set)); + + /** + * Remove a WaitSet implementation from the list of attached entries. + * Does nothing if wait_set was not attached to this notifier. + * @param wait_set WaitSet implementation to remove from the list. + */ + MOCK_METHOD1(detach_from, void(WaitSetImpl * wait_set)); + + /** + * Wake up all the WaitSet implementations attached to this notifier. + */ + MOCK_METHOD0(notify, void()); + + /** + * Inform all the WaitSet implementations attached to this notifier that + * a condition is going to be deleted. + * @param condition The Condition being deleted. + */ + MOCK_METHOD1(will_be_deleted, void(const Condition& condition)); +}; + +} // namespace detail +} // namespace dds +} // namespace fastdds +} // namespace eprosima + +#endif // _FASTDDS_CORE_CONDITION_CONDITIONNOTIFIER_HPP_