Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
985e320de3 | ||
|
|
eac2e8a7dd | ||
|
|
cff2711aba | ||
|
|
608e2f2e56 | ||
|
|
bd695f4a3b | ||
|
|
74162b19bf | ||
|
|
e217532817 | ||
|
|
a7b8ada6a7 | ||
|
|
df09c6e77d | ||
|
|
06ae92ae96 | ||
|
|
99b3803ce2 | ||
|
|
bbe2b99893 | ||
|
|
09e5dd70f4 | ||
|
|
5551facd9b | ||
|
|
38ed9ed172 | ||
|
|
fad351ff85 | ||
|
|
08c6cd9f9a | ||
|
|
830a1f0212 | ||
|
|
1b4485443d | ||
|
|
d73fc2a86a |
13
.github/workflows/mirror-rolling-to-master.yaml
vendored
13
.github/workflows/mirror-rolling-to-master.yaml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Mirror rolling to master
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ rolling ]
|
||||
|
||||
jobs:
|
||||
mirror-to-master:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: zofrex/mirror-branch@v1
|
||||
with:
|
||||
target-branch: master
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file was generated by https://github.com/audrow/update-ros2-repos
|
||||
* @ivanpauno @hidmic @wjwwood
|
||||
@@ -2,6 +2,128 @@
|
||||
Changelog for package rclcpp
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
28.1.6 (2024-12-18)
|
||||
-------------------
|
||||
* apply actual QoS from rmw to the IPC publisher. (`#2707 <https://github.com/ros2/rclcpp/issues/2707>`_) (`#2712 <https://github.com/ros2/rclcpp/issues/2712>`_)
|
||||
* apply actual QoS from rmw to the IPC publisher.
|
||||
* address uncrustify warning.
|
||||
---------
|
||||
(cherry picked from commit 016cfeac99e4b67f58abdf247e57f05b85c09ec4)
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
* Adding in topic name to logging on IPC issues (`#2706 <https://github.com/ros2/rclcpp/issues/2706>`_) (`#2710 <https://github.com/ros2/rclcpp/issues/2710>`_)
|
||||
* Adding in topic name to logging on IPC issues
|
||||
* Update test matching output logging
|
||||
* adding in single quotes
|
||||
---------
|
||||
(cherry picked from commit a13e16e2cbaeacb14ff31272d01cbb21bd8ac037)
|
||||
Co-authored-by: Steve Macenski <stevenmacenski@gmail.com>
|
||||
* enable testRaceConditionAddNode for rmw_connextdds. (`#2698 <https://github.com/ros2/rclcpp/issues/2698>`_)
|
||||
* Re-enable executor test on rmw_connextdds. (`#2693 <https://github.com/ros2/rclcpp/issues/2693>`_) (`#2695 <https://github.com/ros2/rclcpp/issues/2695>`_)
|
||||
It supports the events executor now, so re-enable the test.
|
||||
(cherry picked from commit d7245365ed867db9b309ed3efbfb0391bda09bd5)
|
||||
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
|
||||
* Fix warnings on Windows. (backport `#2692 <https://github.com/ros2/rclcpp/issues/2692>`_) (`#2694 <https://github.com/ros2/rclcpp/issues/2694>`_)
|
||||
* Fix warnings on Windows. (`#2692 <https://github.com/ros2/rclcpp/issues/2692>`_)
|
||||
For reasons I admit I do not understand, the deprecation
|
||||
warnings for StaticSingleThreadedExecutor on Windows
|
||||
happen when we construct a shared_ptr for it in the tests.
|
||||
If we construct a regular object, then it is fine. Luckily
|
||||
this test does not require a shared_ptr, so just make it
|
||||
a regular object here, which rixes the warning.
|
||||
While we are in here, make all of the tests camel case to
|
||||
be consistent.
|
||||
(cherry picked from commit 3310f9eaed967e0c18d17bb2f82d2def838bb7a5)
|
||||
# Conflicts:
|
||||
# rclcpp/test/rclcpp/executors/test_executors.cpp
|
||||
* resolve backport conflict.
|
||||
---------
|
||||
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
* Omnibus fixes for running tests with Connext. (backport `#2684 <https://github.com/ros2/rclcpp/issues/2684>`_) (`#2690 <https://github.com/ros2/rclcpp/issues/2690>`_)
|
||||
* Omnibus fixes for running tests with Connext. (`#2684 <https://github.com/ros2/rclcpp/issues/2684>`_)
|
||||
* Omnibus fixes for running tests with Connext.
|
||||
When running the tests with RTI Connext as the default
|
||||
RMW, some of the tests are failing. There are three
|
||||
different failures fixed here:
|
||||
1. Setting the liveliness duration to a value smaller than
|
||||
a microsecond causes Connext to throw an error. Set it to
|
||||
a millisecond.
|
||||
2. Using the SystemDefaultsQoS sets the QoS to KEEP_LAST 1.
|
||||
Connext is somewhat slow in this regard, so it can be the case
|
||||
that we are overwriting a previous service introspection event
|
||||
with the next one. Switch to the ServicesDefaultQoS in the test,
|
||||
which ensures we will not lose events.
|
||||
3. Connext is slow to match publishers and subscriptions. Thus,
|
||||
when creating a subscription "on-the-fly", we should wait for the
|
||||
publisher to match it before expecting the subscription to actually
|
||||
receive data from it.
|
||||
With these fixes in place, the test_client_common, test_generic_service,
|
||||
test_service_introspection, and test_executors tests all pass for
|
||||
me with rmw_connextdds.
|
||||
* Fixes for executors.
|
||||
* One more fix for services.
|
||||
* More fixes for service_introspection.
|
||||
* More fixes for introspection.
|
||||
---------
|
||||
(cherry picked from commit 9984197c292d6c5ae0e7661aaea245ffb0fea057)
|
||||
# Conflicts:
|
||||
# rclcpp/test/rclcpp/executors/test_executors.cpp
|
||||
# rclcpp/test/rclcpp/test_generic_service.cpp
|
||||
* address backport merge conflicts.
|
||||
---------
|
||||
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
* fix(Executor): Fix segfault if callback group is deleted during rmw_wait (`#2682 <https://github.com/ros2/rclcpp/issues/2682>`_)
|
||||
* Fix NodeOptions assignment operator (`#2656 <https://github.com/ros2/rclcpp/issues/2656>`_) (`#2660 <https://github.com/ros2/rclcpp/issues/2660>`_)
|
||||
* Fix NodeOptions assignment operator
|
||||
Also copy the enable_logger_service\_ member during the assignment operation
|
||||
* Add more checks for NodeOptions copy test
|
||||
* Set non default values by avoiding the copy-assignement
|
||||
Co-authored-by: Christophe Bedard <bedard.christophe@gmail.com>
|
||||
(cherry picked from commit 9b654942f99f17850e0e95480958abdbb508bc00)
|
||||
Co-authored-by: Romain DESILLE <r.desille@gmail.com>
|
||||
* set QoS History KEEP_ALL explicitly for statistics publisher. (`#2650 <https://github.com/ros2/rclcpp/issues/2650>`_) (`#2657 <https://github.com/ros2/rclcpp/issues/2657>`_)
|
||||
* set QoS History KEEP_ALL explicitly for statistics publisher.
|
||||
* test_subscription_options adjustment.
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
* Contributors: Cristóbal Arroyo, Tomoya Fujita, jmachowinski, mergify[bot]
|
||||
|
||||
28.1.5 (2024-09-19)
|
||||
-------------------
|
||||
* backport fix events-executor warm-up bug and add unit-tests (`#2591 <https://github.com/ros2/rclcpp/issues/2591>`_) (`#2628 <https://github.com/ros2/rclcpp/issues/2628>`_)
|
||||
* Contributors: Alberto Soragna
|
||||
|
||||
28.1.4 (2024-09-06)
|
||||
-------------------
|
||||
* Split test_executors.cpp even further. (`#2572 <https://github.com/ros2/rclcpp/issues/2572>`_) (`#2619 <https://github.com/ros2/rclcpp/issues/2619>`_)
|
||||
That's because it is too large for Windows Debug to compile,
|
||||
so split into smaller bits.
|
||||
Even with this split, the file is too big; that's likely
|
||||
because we are using TYPED_TEST here, which generates multiple
|
||||
symbols per test case. To deal with this, without further
|
||||
breaking up the file, also add in the /bigobj flag when
|
||||
compiling on Windows Debug.
|
||||
(cherry picked from commit c743c173e68d92af872cf163e10721a8dbe51dd0)
|
||||
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
|
||||
* Correct node name in service test code (`#2615 <https://github.com/ros2/rclcpp/issues/2615>`_) (`#2616 <https://github.com/ros2/rclcpp/issues/2616>`_)
|
||||
(cherry picked from commit e846f56224a39b93f1c609e7ee03fff0662b7453)
|
||||
Co-authored-by: Barry Xu <barry.xu@sony.com>
|
||||
* Release ownership of entities after spinning cancelled (backport `#2556 <https://github.com/ros2/rclcpp/issues/2556>`_) (`#2580 <https://github.com/ros2/rclcpp/issues/2580>`_)
|
||||
* Release ownership of entities after spinning cancelled (`#2556 <https://github.com/ros2/rclcpp/issues/2556>`_)
|
||||
* Release ownership of entities after spinning cancelled
|
||||
* Move release action to every exit point in different spin functions
|
||||
* Move wait_result\_.reset() before setting spinning to false
|
||||
* Update test code
|
||||
* Move test code to test_executors.cpp
|
||||
---------
|
||||
(cherry picked from commit 069a0018935b33a14632a1cdf4074984a1cf80fe)
|
||||
# Conflicts:
|
||||
# rclcpp/test/rclcpp/executors/test_executors.cpp
|
||||
* Fix backport issue (`#2581 <https://github.com/ros2/rclcpp/issues/2581>`_)
|
||||
---------
|
||||
Co-authored-by: Barry Xu <barry.xu@sony.com>
|
||||
* Contributors: mergify[bot]
|
||||
|
||||
28.1.3 (2024-06-27)
|
||||
-------------------
|
||||
* Add test creating two content filter topics with the same topic name (`#2546 <https://github.com/ros2/rclcpp/issues/2546>`_) (`#2549 <https://github.com/ros2/rclcpp/issues/2549>`_) (`#2552 <https://github.com/ros2/rclcpp/issues/2552>`_)
|
||||
|
||||
@@ -162,8 +162,7 @@ public:
|
||||
const rclcpp::QoS & qos,
|
||||
const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
|
||||
{
|
||||
// Topic is unused for now.
|
||||
(void)topic;
|
||||
(void)qos;
|
||||
(void)options;
|
||||
|
||||
// If needed, setup intra process communication.
|
||||
@@ -171,22 +170,26 @@ public:
|
||||
auto context = node_base->get_context();
|
||||
// Get the intra process manager instance for this context.
|
||||
auto ipm = context->get_sub_context<rclcpp::experimental::IntraProcessManager>();
|
||||
// Register the publisher with the intra process manager.
|
||||
if (qos.history() != rclcpp::HistoryPolicy::KeepLast) {
|
||||
// Check if the QoS is compatible with intra-process.
|
||||
auto qos_profile = get_actual_qos();
|
||||
if (qos_profile.history() != rclcpp::HistoryPolicy::KeepLast) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication allowed only with keep last history qos policy");
|
||||
"intraprocess communication on topic '" + topic +
|
||||
"' allowed only with keep last history qos policy");
|
||||
}
|
||||
if (qos.depth() == 0) {
|
||||
if (qos_profile.depth() == 0) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication is not allowed with a zero qos history depth value");
|
||||
"intraprocess communication on topic '" + topic +
|
||||
"' is not allowed with a zero qos history depth value");
|
||||
}
|
||||
if (qos.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
|
||||
if (qos_profile.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
|
||||
buffer_ = rclcpp::experimental::create_intra_process_buffer<
|
||||
ROSMessageType, ROSMessageTypeAllocator, ROSMessageTypeDeleter>(
|
||||
rclcpp::detail::resolve_intra_process_buffer_type(options_.intra_process_buffer_type),
|
||||
qos,
|
||||
qos_profile,
|
||||
std::make_shared<ROSMessageTypeAllocator>(ros_message_type_allocator_));
|
||||
}
|
||||
// Register the publisher with the intra process manager.
|
||||
uint64_t intra_process_publisher_id = ipm->add_publisher(this->shared_from_this(), buffer_);
|
||||
this->setup_intra_process(
|
||||
intra_process_publisher_id,
|
||||
|
||||
@@ -159,11 +159,13 @@ public:
|
||||
auto qos_profile = get_actual_qos();
|
||||
if (qos_profile.history() != rclcpp::HistoryPolicy::KeepLast) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication allowed only with keep last history qos policy");
|
||||
"intraprocess communication on topic '" + topic_name +
|
||||
"' allowed only with keep last history qos policy");
|
||||
}
|
||||
if (qos_profile.depth() == 0) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication is not allowed with 0 depth qos policy");
|
||||
"intraprocess communication on topic '" + topic_name +
|
||||
"' is not allowed with 0 depth qos policy");
|
||||
}
|
||||
|
||||
using SubscriptionIntraProcessT = rclcpp::experimental::SubscriptionIntraProcess<
|
||||
|
||||
@@ -80,7 +80,8 @@ struct SubscriptionOptionsBase
|
||||
|
||||
// An optional QoS which can provide topic_statistics with a stable QoS separate from
|
||||
// the subscription's current QoS settings which could be unstable.
|
||||
rclcpp::QoS qos = SystemDefaultsQoS();
|
||||
// Explicitly set the enough depth to avoid missing the statistics messages.
|
||||
rclcpp::QoS qos = SystemDefaultsQoS().keep_last(10);
|
||||
};
|
||||
|
||||
TopicStatisticsOptions topic_stats_options;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="2">
|
||||
<name>rclcpp</name>
|
||||
<version>28.1.3</version>
|
||||
<version>28.1.6</version>
|
||||
<description>The ROS client library in C++.</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -275,7 +275,7 @@ Executor::spin_until_future_complete_impl(
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin_until_future_complete() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););
|
||||
while (rclcpp::ok(this->context_) && spinning.load()) {
|
||||
// Do one item of work.
|
||||
spin_once_impl(timeout_left);
|
||||
@@ -364,7 +364,7 @@ Executor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin_some() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););
|
||||
|
||||
// clear the wait result and wait for work without blocking to collect the work
|
||||
// for the first time
|
||||
@@ -431,7 +431,7 @@ Executor::spin_once(std::chrono::nanoseconds timeout)
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin_once() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););
|
||||
spin_once_impl(timeout);
|
||||
}
|
||||
|
||||
@@ -729,13 +729,33 @@ Executor::wait_for_work(std::chrono::nanoseconds timeout)
|
||||
// Clear any previous wait result
|
||||
this->wait_result_.reset();
|
||||
|
||||
// we need to make sure that callback groups don't get out of scope
|
||||
// during the wait. As in jazzy, they are not covered by the DynamicStorage,
|
||||
// we explicitly hold them here as a bugfix
|
||||
std::vector<rclcpp::CallbackGroup::SharedPtr> cbgs;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {
|
||||
this->collect_entities();
|
||||
}
|
||||
|
||||
auto callback_groups = this->collector_.get_all_callback_groups();
|
||||
cbgs.resize(callback_groups.size());
|
||||
for(const auto & w_ptr : callback_groups) {
|
||||
auto shr_ptr = w_ptr.lock();
|
||||
if(shr_ptr) {
|
||||
cbgs.push_back(std::move(shr_ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->wait_result_.emplace(wait_set_.wait(timeout));
|
||||
|
||||
// drop references to the callback groups, before trying to execute anything
|
||||
cbgs.clear();
|
||||
|
||||
if (!this->wait_result_ || this->wait_result_->kind() == WaitResultKind::Empty) {
|
||||
RCUTILS_LOG_WARN_NAMED(
|
||||
"rclcpp",
|
||||
|
||||
@@ -55,7 +55,7 @@ MultiThreadedExecutor::spin()
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false););
|
||||
RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););
|
||||
std::vector<std::thread> threads;
|
||||
size_t thread_id = 0;
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ SingleThreadedExecutor::spin()
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););
|
||||
|
||||
// Clear any previous result and rebuild the waitset
|
||||
this->wait_result_.reset();
|
||||
|
||||
@@ -110,10 +110,29 @@ StaticSingleThreadedExecutor::spin_once_impl(std::chrono::nanoseconds timeout)
|
||||
std::optional<rclcpp::WaitResult<rclcpp::WaitSet>>
|
||||
StaticSingleThreadedExecutor::collect_and_wait(std::chrono::nanoseconds timeout)
|
||||
{
|
||||
// we need to make sure that callback groups don't get out of scope
|
||||
// during the wait. As in jazzy, they are not covered by the DynamicStorage,
|
||||
// we explicitly hold them here as a bugfix
|
||||
std::vector<rclcpp::CallbackGroup::SharedPtr> cbgs;
|
||||
|
||||
if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {
|
||||
this->collect_entities();
|
||||
}
|
||||
|
||||
auto callback_groups = this->collector_.get_all_callback_groups();
|
||||
cbgs.resize(callback_groups.size());
|
||||
for(const auto & w_ptr : callback_groups) {
|
||||
auto shr_ptr = w_ptr.lock();
|
||||
if(shr_ptr) {
|
||||
cbgs.push_back(std::move(shr_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
auto wait_result = wait_set_.wait(std::chrono::nanoseconds(timeout));
|
||||
|
||||
// drop references to the callback groups, before trying to execute anything
|
||||
cbgs.clear();
|
||||
|
||||
if (wait_result.kind() == WaitResultKind::Empty) {
|
||||
RCUTILS_LOG_WARN_NAMED(
|
||||
"rclcpp",
|
||||
|
||||
@@ -62,7 +62,6 @@ EventsExecutor::EventsExecutor(
|
||||
// ---> we need to wake up the executor so that it can terminate
|
||||
// - a node or callback group guard condition is triggered:
|
||||
// ---> the entities collection is changed, we need to update callbacks
|
||||
notify_waitable_event_pushed_ = false;
|
||||
this->refresh_current_collection_from_callback_groups();
|
||||
});
|
||||
|
||||
@@ -164,6 +163,14 @@ EventsExecutor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhau
|
||||
return false;
|
||||
};
|
||||
|
||||
// If this spin is not exhaustive (e.g. spin_some), we need to explicitly check
|
||||
// if entities need to be rebuilt here rather than letting the notify waitable event do it.
|
||||
// A non-exhaustive spin would not check for work a second time, thus delaying the execution
|
||||
// of some entities to the next invocation of spin.
|
||||
if (!exhaustive) {
|
||||
this->refresh_current_collection_from_callback_groups();
|
||||
}
|
||||
|
||||
// Get the number of events and timers ready at start
|
||||
const size_t ready_events_at_start = events_queue_->size();
|
||||
size_t executed_events = 0;
|
||||
@@ -403,6 +410,16 @@ EventsExecutor::get_automatically_added_callback_groups_from_nodes()
|
||||
void
|
||||
EventsExecutor::refresh_current_collection_from_callback_groups()
|
||||
{
|
||||
// Do not rebuild if we don't need to.
|
||||
// A rebuild event could be generated, but then
|
||||
// this function could end up being called from somewhere else
|
||||
// before that event gets processed, for example if
|
||||
// a node or callback group is manually added to the executor.
|
||||
const bool notify_waitable_triggered = notify_waitable_event_pushed_.exchange(false);
|
||||
if (!notify_waitable_triggered && !this->entities_collector_->has_pending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the new collection
|
||||
this->entities_collector_->update_collections();
|
||||
auto callback_groups = this->entities_collector_->get_all_callback_groups();
|
||||
|
||||
@@ -80,6 +80,7 @@ NodeOptions::operator=(const NodeOptions & other)
|
||||
this->clock_type_ = other.clock_type_;
|
||||
this->clock_qos_ = other.clock_qos_;
|
||||
this->use_clock_thread_ = other.use_clock_thread_;
|
||||
this->enable_logger_service_ = other.enable_logger_service_;
|
||||
this->parameter_event_qos_ = other.parameter_event_qos_;
|
||||
this->rosout_qos_ = other.rosout_qos_;
|
||||
this->parameter_event_publisher_options_ = other.parameter_event_publisher_options_;
|
||||
|
||||
@@ -462,8 +462,12 @@ endif()
|
||||
ament_add_gtest(
|
||||
test_executors
|
||||
executors/test_executors.cpp
|
||||
executors/test_waitable.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 180)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND MSVC)
|
||||
target_compile_options(test_executors PRIVATE "/bigobj")
|
||||
endif()
|
||||
if(TARGET test_executors)
|
||||
target_link_libraries(test_executors ${PROJECT_NAME} rcl::rcl ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -495,6 +499,26 @@ if(TARGET test_executors)
|
||||
target_link_libraries(test_executors_intraprocess ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(
|
||||
test_executors_busy_waiting
|
||||
executors/test_executors_busy_waiting.cpp
|
||||
executors/test_waitable.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 180)
|
||||
if(TARGET test_executors_busy_waiting)
|
||||
target_link_libraries(test_executors_busy_waiting ${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(
|
||||
test_executors_warmup
|
||||
executors/test_executors_warmup.cpp
|
||||
executors/test_waitable.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 180)
|
||||
if(TARGET test_executors_warmup)
|
||||
target_link_libraries(test_executors_warmup ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_static_single_threaded_executor executors/test_static_single_threaded_executor.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}")
|
||||
if(TARGET test_static_single_threaded_executor)
|
||||
@@ -598,7 +622,7 @@ ament_add_gtest(test_executor test_executor.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 120)
|
||||
if(TARGET test_executor)
|
||||
target_link_libraries(test_executor ${PROJECT_NAME} mimick)
|
||||
target_link_libraries(test_executor ${PROJECT_NAME} mimick ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_graph_listener test_graph_listener.cpp)
|
||||
|
||||
@@ -39,8 +39,10 @@
|
||||
#include "rclcpp/time_source.hpp"
|
||||
|
||||
#include "test_msgs/msg/empty.hpp"
|
||||
#include "test_msgs/srv/empty.hpp"
|
||||
|
||||
#include "./executor_types.hpp"
|
||||
#include "./test_waitable.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -331,114 +333,6 @@ TYPED_TEST(TestExecutors, testSpinUntilFutureCompleteWithTimeout)
|
||||
spinner.join();
|
||||
}
|
||||
|
||||
class TestWaitable : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
TestWaitable() = default;
|
||||
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
if (trigger_count_ > 0) {
|
||||
// Keep the gc triggered until the trigger count is reduced back to zero.
|
||||
// This is necessary if trigger() results in the wait set waking, but not
|
||||
// executing this waitable, in which case it needs to be re-triggered.
|
||||
gc_.trigger();
|
||||
}
|
||||
rclcpp::detail::add_guard_condition_to_rcl_wait_set(wait_set, gc_);
|
||||
}
|
||||
|
||||
void trigger()
|
||||
{
|
||||
trigger_count_++;
|
||||
gc_.trigger();
|
||||
}
|
||||
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
is_ready_count_++;
|
||||
for (size_t i = 0; i < wait_set.size_of_guard_conditions; ++i) {
|
||||
auto rcl_guard_condition = wait_set.guard_conditions[i];
|
||||
if (&gc_.get_rcl_guard_condition() == rcl_guard_condition) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override
|
||||
{
|
||||
(void) id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
execute(const std::shared_ptr<void> &) override
|
||||
{
|
||||
trigger_count_--;
|
||||
count_++;
|
||||
if (nullptr != on_execute_callback_) {
|
||||
on_execute_callback_();
|
||||
} else {
|
||||
// TODO(wjwwood): I don't know why this was here, but probably it should
|
||||
// not be there, or test cases where that is important should use the
|
||||
// on_execute_callback?
|
||||
std::this_thread::sleep_for(3ms);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
set_on_execute_callback(std::function<void()> on_execute_callback)
|
||||
{
|
||||
on_execute_callback_ = on_execute_callback;
|
||||
}
|
||||
|
||||
void
|
||||
set_on_ready_callback(std::function<void(size_t, int)> callback) override
|
||||
{
|
||||
auto gc_callback = [callback](size_t count) {
|
||||
callback(count, 0);
|
||||
};
|
||||
gc_.set_on_trigger_callback(gc_callback);
|
||||
}
|
||||
|
||||
void
|
||||
clear_on_ready_callback() override
|
||||
{
|
||||
gc_.set_on_trigger_callback(nullptr);
|
||||
}
|
||||
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override {return 1;}
|
||||
|
||||
size_t
|
||||
get_count() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
|
||||
size_t
|
||||
get_is_ready_call_count() const
|
||||
{
|
||||
return is_ready_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<size_t> trigger_count_ = 0;
|
||||
std::atomic<size_t> is_ready_count_ = 0;
|
||||
std::atomic<size_t> count_ = 0;
|
||||
rclcpp::GuardCondition gc_;
|
||||
std::function<void()> on_execute_callback_ = nullptr;
|
||||
};
|
||||
|
||||
TYPED_TEST(TestExecutors, spinAll)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
@@ -494,7 +388,7 @@ to_nanoseconds_helper(DurationT duration)
|
||||
// - works nominally (it can execute entities)
|
||||
// - it can execute multiple items at once
|
||||
// - it does not wait for work to be available before returning
|
||||
TYPED_TEST(TestExecutors, spin_some)
|
||||
TYPED_TEST(TestExecutors, spinSome)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
@@ -586,7 +480,7 @@ TYPED_TEST(TestExecutors, spin_some)
|
||||
|
||||
// The purpose of this test is to check that the ExecutorT.spin_some() method:
|
||||
// - does not continue executing after max_duration has elapsed
|
||||
TYPED_TEST(TestExecutors, spin_some_max_duration)
|
||||
TYPED_TEST(TestExecutors, spinSomeMaxDuration)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
@@ -753,13 +647,6 @@ TYPED_TEST(TestExecutors, testSpinUntilFutureCompleteInterrupted)
|
||||
TYPED_TEST(TestExecutors, testRaceConditionAddNode)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
// rmw_connextdds doesn't support events-executor
|
||||
if (
|
||||
std::is_same<ExecutorType, rclcpp::experimental::executors::EventsExecutor>() &&
|
||||
std::string(rmw_get_implementation_identifier()).find("rmw_connextdds") == 0)
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
// Spawn some threads to do some heavy work
|
||||
std::atomic<bool> should_cancel = false;
|
||||
@@ -878,154 +765,59 @@ TEST(TestExecutors, testSpinWithNonDefaultContext)
|
||||
rclcpp::shutdown(non_default_context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class TestBusyWaiting : public ::testing::Test
|
||||
TYPED_TEST(TestExecutors, releaseOwnershipEntityAfterSpinningCancel)
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
const auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
std::stringstream test_name;
|
||||
test_name << test_info->test_case_name() << "_" << test_info->name();
|
||||
node = std::make_shared<rclcpp::Node>("node", test_name.str());
|
||||
callback_group = node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
/* automatically_add_to_executor_with_node =*/ false);
|
||||
auto future = std::async(std::launch::async, [&executor] {executor.spin();});
|
||||
|
||||
auto waitable_interfaces = node->get_node_waitables_interface();
|
||||
waitable = std::make_shared<TestWaitable>();
|
||||
waitable_interfaces->add_waitable(waitable, callback_group);
|
||||
|
||||
executor = std::make_shared<T>();
|
||||
executor->add_callback_group(callback_group, node->get_node_base_interface());
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node");
|
||||
auto callback = [](
|
||||
const test_msgs::srv::Empty::Request::SharedPtr, test_msgs::srv::Empty::Response::SharedPtr) {
|
||||
};
|
||||
auto server = node->create_service<test_msgs::srv::Empty>("test_service", callback);
|
||||
while (!executor.is_spinning()) {
|
||||
std::this_thread::sleep_for(50ms);
|
||||
}
|
||||
executor.add_node(node);
|
||||
std::this_thread::sleep_for(50ms);
|
||||
executor.cancel();
|
||||
std::future_status future_status = future.wait_for(1s);
|
||||
EXPECT_EQ(future_status, std::future_status::ready);
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void
|
||||
set_up_and_trigger_waitable(std::function<void()> extra_callback = nullptr)
|
||||
{
|
||||
this->has_executed = false;
|
||||
this->waitable->set_on_execute_callback([this, extra_callback]() {
|
||||
if (!this->has_executed) {
|
||||
// trigger once to see if the second trigger is handled or not
|
||||
// this follow up trigger simulates new entities becoming ready while
|
||||
// the executor is executing something else, e.g. subscription got data
|
||||
// or a timer expired, etc.
|
||||
// spin_some would not handle this second trigger, since it collects
|
||||
// work only once, whereas spin_all should handle it since it
|
||||
// collects work multiple times
|
||||
this->waitable->trigger();
|
||||
this->has_executed = true;
|
||||
}
|
||||
if (nullptr != extra_callback) {
|
||||
extra_callback();
|
||||
}
|
||||
});
|
||||
this->waitable->trigger();
|
||||
}
|
||||
|
||||
void
|
||||
check_for_busy_waits(std::chrono::steady_clock::time_point start_time)
|
||||
{
|
||||
// rough time based check, since the work to be done was very small it
|
||||
// should be safe to check that we didn't use more than half the
|
||||
// max duration, which itself is much larger than necessary
|
||||
// however, it could still produce a false-positive
|
||||
EXPECT_LT(
|
||||
std::chrono::steady_clock::now() - start_time,
|
||||
max_duration / 2)
|
||||
<< "executor took a long time to execute when it should have done "
|
||||
<< "nothing and should not have blocked either, but this could be a "
|
||||
<< "false negative if the computer is really slow";
|
||||
|
||||
// this check is making some assumptions about the implementation of the
|
||||
// executors, but it should be safe to say that a busy wait may result in
|
||||
// hundreds or thousands of calls to is_ready(), but "normal" executor
|
||||
// behavior should be within an order of magnitude of the number of
|
||||
// times that the waitable was executed
|
||||
ASSERT_LT(waitable->get_is_ready_call_count(), 10u * this->waitable->get_count());
|
||||
}
|
||||
|
||||
static constexpr auto max_duration = 10s;
|
||||
|
||||
rclcpp::Node::SharedPtr node;
|
||||
rclcpp::CallbackGroup::SharedPtr callback_group;
|
||||
std::shared_ptr<TestWaitable> waitable;
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
std::shared_ptr<T> executor;
|
||||
bool has_executed;
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(TestBusyWaiting, ExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin_all)
|
||||
{
|
||||
this->set_up_and_trigger_waitable();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
this->executor->spin_all(this->max_duration);
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the initial trigger, and the follow up from in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 2u);
|
||||
EXPECT_EQ(server.use_count(), 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin_some)
|
||||
TYPED_TEST(TestExecutors, testRaceDropCallbackGroupFromSecondThread)
|
||||
{
|
||||
this->set_up_and_trigger_waitable();
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
this->executor->spin_some(this->max_duration);
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the inital trigger, but not the follow up in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 1u);
|
||||
}
|
||||
// Create an executor
|
||||
ExecutorType executor;
|
||||
executor.add_node(this->node);
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin)
|
||||
{
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
bool first_check_passed = false;
|
||||
|
||||
this->set_up_and_trigger_waitable([&cv, &cv_m, &first_check_passed]() {
|
||||
cv.notify_one();
|
||||
if (!first_check_passed) {
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 1s, [&]() {return first_check_passed;});
|
||||
}
|
||||
});
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
std::thread t([this]() {
|
||||
this->executor->spin();
|
||||
// Start spinning
|
||||
auto executor_thread = std::thread(
|
||||
[&executor]() {
|
||||
executor.spin();
|
||||
});
|
||||
|
||||
// wait until thread has started (first execute of waitable)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 10s);
|
||||
// As the problem is a race, we do this multiple times,
|
||||
// to raise our chances of hitting the problem
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto cg = this->node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive);
|
||||
auto timer = this->node->create_timer(1s, [] {}, cg);
|
||||
// sleep a bit, so that the spin thread can pick up the callback group
|
||||
// and add it to the executor
|
||||
std::this_thread::sleep_for(5ms);
|
||||
|
||||
// At this point the callbackgroup should be used within the waitset of the executor
|
||||
// as we leave the scope, the reference to cg will be dropped.
|
||||
// If the executor has a race, we will experience a segfault at this point.
|
||||
}
|
||||
EXPECT_GT(this->waitable->get_count(), 0u);
|
||||
|
||||
first_check_passed = true;
|
||||
cv.notify_one();
|
||||
|
||||
// wait until the executor has finished (second execute of waitable)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 10s);
|
||||
}
|
||||
EXPECT_EQ(this->waitable->get_count(), 2u);
|
||||
|
||||
this->executor->cancel();
|
||||
t.join();
|
||||
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the initial trigger, and the follow up from in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 2u);
|
||||
executor.cancel();
|
||||
executor_thread.join();
|
||||
}
|
||||
|
||||
181
rclcpp/test/rclcpp/executors/test_executors_busy_waiting.cpp
Normal file
181
rclcpp/test/rclcpp/executors/test_executors_busy_waiting.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright 2024 Open Source Robotics Foundation, Inc.
|
||||
//
|
||||
// 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 <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
|
||||
#include "./executor_types.hpp"
|
||||
#include "./test_waitable.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
template<typename T>
|
||||
class TestBusyWaiting : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
|
||||
const auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
std::stringstream test_name;
|
||||
test_name << test_info->test_case_name() << "_" << test_info->name();
|
||||
node = std::make_shared<rclcpp::Node>("node", test_name.str());
|
||||
callback_group = node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
/* automatically_add_to_executor_with_node =*/ false);
|
||||
|
||||
auto waitable_interfaces = node->get_node_waitables_interface();
|
||||
waitable = std::make_shared<TestWaitable>();
|
||||
waitable_interfaces->add_waitable(waitable, callback_group);
|
||||
|
||||
executor = std::make_shared<T>();
|
||||
executor->add_callback_group(callback_group, node->get_node_base_interface());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void
|
||||
set_up_and_trigger_waitable(std::function<void()> extra_callback = nullptr)
|
||||
{
|
||||
this->has_executed = false;
|
||||
this->waitable->set_on_execute_callback([this, extra_callback]() {
|
||||
if (!this->has_executed) {
|
||||
// trigger once to see if the second trigger is handled or not
|
||||
// this follow up trigger simulates new entities becoming ready while
|
||||
// the executor is executing something else, e.g. subscription got data
|
||||
// or a timer expired, etc.
|
||||
// spin_some would not handle this second trigger, since it collects
|
||||
// work only once, whereas spin_all should handle it since it
|
||||
// collects work multiple times
|
||||
this->waitable->trigger();
|
||||
this->has_executed = true;
|
||||
}
|
||||
if (nullptr != extra_callback) {
|
||||
extra_callback();
|
||||
}
|
||||
});
|
||||
this->waitable->trigger();
|
||||
}
|
||||
|
||||
void
|
||||
check_for_busy_waits(std::chrono::steady_clock::time_point start_time)
|
||||
{
|
||||
// rough time based check, since the work to be done was very small it
|
||||
// should be safe to check that we didn't use more than half the
|
||||
// max duration, which itself is much larger than necessary
|
||||
// however, it could still produce a false-positive
|
||||
EXPECT_LT(
|
||||
std::chrono::steady_clock::now() - start_time,
|
||||
max_duration / 2)
|
||||
<< "executor took a long time to execute when it should have done "
|
||||
<< "nothing and should not have blocked either, but this could be a "
|
||||
<< "false negative if the computer is really slow";
|
||||
|
||||
// this check is making some assumptions about the implementation of the
|
||||
// executors, but it should be safe to say that a busy wait may result in
|
||||
// hundreds or thousands of calls to is_ready(), but "normal" executor
|
||||
// behavior should be within an order of magnitude of the number of
|
||||
// times that the waitable was executed
|
||||
ASSERT_LT(waitable->get_is_ready_call_count(), 10u * this->waitable->get_count());
|
||||
}
|
||||
|
||||
static constexpr auto max_duration = 10s;
|
||||
|
||||
rclcpp::Node::SharedPtr node;
|
||||
rclcpp::CallbackGroup::SharedPtr callback_group;
|
||||
std::shared_ptr<TestWaitable> waitable;
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
std::shared_ptr<T> executor;
|
||||
bool has_executed;
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(TestBusyWaiting, ExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin_all)
|
||||
{
|
||||
this->set_up_and_trigger_waitable();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
this->executor->spin_all(this->max_duration);
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the initial trigger, and the follow up from in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 2u);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin_some)
|
||||
{
|
||||
this->set_up_and_trigger_waitable();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
this->executor->spin_some(this->max_duration);
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the inital trigger, but not the follow up in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 1u);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin)
|
||||
{
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
bool first_check_passed = false;
|
||||
|
||||
this->set_up_and_trigger_waitable([&cv, &cv_m, &first_check_passed]() {
|
||||
cv.notify_one();
|
||||
if (!first_check_passed) {
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 1s, [&]() {return first_check_passed;});
|
||||
}
|
||||
});
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
std::thread t([this]() {
|
||||
this->executor->spin();
|
||||
});
|
||||
|
||||
// wait until thread has started (first execute of waitable)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 10s);
|
||||
}
|
||||
EXPECT_GT(this->waitable->get_count(), 0u);
|
||||
|
||||
first_check_passed = true;
|
||||
cv.notify_one();
|
||||
|
||||
// wait until the executor has finished (second execute of waitable)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 10s);
|
||||
}
|
||||
EXPECT_EQ(this->waitable->get_count(), 2u);
|
||||
|
||||
this->executor->cancel();
|
||||
t.join();
|
||||
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the initial trigger, and the follow up from in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 2u);
|
||||
}
|
||||
244
rclcpp/test/rclcpp/executors/test_executors_warmup.cpp
Normal file
244
rclcpp/test/rclcpp/executors/test_executors_warmup.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
// Copyright 2024 Open Source Robotics Foundation, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* This test checks all implementations of rclcpp::executor to check they pass they basic API
|
||||
* tests. Anything specific to any executor in particular should go in a separate test file.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
|
||||
#include "test_msgs/msg/empty.hpp"
|
||||
|
||||
#include "./executor_types.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
template<typename T>
|
||||
class TestExecutorsWarmup : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp()
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(TestExecutorsWarmup, ExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
// This test verifies that spin_all is correctly collecting work multiple times
|
||||
// even when one of the items of work is a notifier waitable event and thus results in
|
||||
// rebuilding the entities collection.
|
||||
// When spin_all goes back to collect more work, it should see the ready items from
|
||||
// the new added entities
|
||||
TYPED_TEST(TestExecutorsWarmup, spin_all_doesnt_require_warmup)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
// Enable intra-process to guarantee deterministic and synchronous delivery of the message / event
|
||||
auto node_options = rclcpp::NodeOptions().use_intra_process_comms(true);
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node", node_options);
|
||||
|
||||
// Add node to the executor before creating the entities
|
||||
executor.add_node(node);
|
||||
|
||||
// Create entities, this will produce a notifier waitable event, telling the executor to refresh
|
||||
// the entities collection
|
||||
auto publisher = node->create_publisher<test_msgs::msg::Empty>("test_topic", rclcpp::QoS(10));
|
||||
size_t callback_count = 0;
|
||||
auto callback = [&callback_count](test_msgs::msg::Empty::ConstSharedPtr) {callback_count++;};
|
||||
auto subscription =
|
||||
node->create_subscription<test_msgs::msg::Empty>(
|
||||
"test_topic", rclcpp::QoS(10), std::move(callback));
|
||||
|
||||
ASSERT_EQ(callback_count, 0u);
|
||||
|
||||
// Publish a message so that the new entities (i.e. the subscriber) already have work to do
|
||||
publisher->publish(test_msgs::msg::Empty());
|
||||
|
||||
// We need to select a duration that is greater than
|
||||
// the time taken to refresh the entities collection and rebuild the waitset.
|
||||
// spin-all is expected to process the notifier waitable event, rebuild the collection,
|
||||
// and then collect more work, finding the subscription message event.
|
||||
// This duration has been selected empirically.
|
||||
executor.spin_all(std::chrono::milliseconds(500));
|
||||
|
||||
// Verify that the callback is called as part of the spin above
|
||||
EXPECT_EQ(callback_count, 1u);
|
||||
}
|
||||
|
||||
// Same test as `spin_all_doesnt_require_warmup`, but uses a callback group
|
||||
// This test reproduces the bug reported by https://github.com/ros2/rclcpp/issues/2589
|
||||
TYPED_TEST(TestExecutorsWarmup, spin_all_doesnt_require_warmup_with_cbgroup)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// TODO(alsora): Enable when https://github.com/ros2/rclcpp/pull/2595 gets merged
|
||||
if (
|
||||
std::is_same<ExecutorType, rclcpp::executors::SingleThreadedExecutor>() ||
|
||||
std::is_same<ExecutorType, rclcpp::executors::MultiThreadedExecutor>())
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
ExecutorType executor;
|
||||
|
||||
// Enable intra-process to guarantee deterministic and synchronous delivery of the message / event
|
||||
auto node_options = rclcpp::NodeOptions().use_intra_process_comms(true);
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node", node_options);
|
||||
|
||||
auto callback_group = node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
false);
|
||||
|
||||
// Add callback group to the executor before creating the entities
|
||||
executor.add_callback_group(callback_group, node->get_node_base_interface());
|
||||
|
||||
// Create entities, this will produce a notifier waitable event, telling the executor to refresh
|
||||
// the entities collection
|
||||
auto publisher = node->create_publisher<test_msgs::msg::Empty>("test_topic", rclcpp::QoS(10));
|
||||
size_t callback_count = 0;
|
||||
auto callback = [&callback_count](test_msgs::msg::Empty::ConstSharedPtr) {callback_count++;};
|
||||
rclcpp::SubscriptionOptions sub_options;
|
||||
sub_options.callback_group = callback_group;
|
||||
auto subscription =
|
||||
node->create_subscription<test_msgs::msg::Empty>(
|
||||
"test_topic", rclcpp::QoS(10), std::move(callback), sub_options);
|
||||
|
||||
ASSERT_EQ(callback_count, 0u);
|
||||
|
||||
// Publish a message so that the new entities (i.e. the subscriber) already have work to do
|
||||
publisher->publish(test_msgs::msg::Empty());
|
||||
|
||||
// We need to select a duration that is greater than
|
||||
// the time taken to refresh the entities collection and rebuild the waitset.
|
||||
// spin-all is expected to process the notifier waitable event, rebuild the collection,
|
||||
// and then collect more work, finding the subscription message event.
|
||||
// This duration has been selected empirically.
|
||||
executor.spin_all(std::chrono::milliseconds(500));
|
||||
|
||||
// Verify that the callback is called as part of the spin above
|
||||
EXPECT_EQ(callback_count, 1u);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutorsWarmup, spin_some_doesnt_require_warmup)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// TODO(alsora): currently only the events-executor passes this test.
|
||||
// Enable single-threaded and multi-threaded executors
|
||||
// when https://github.com/ros2/rclcpp/pull/2595 gets merged
|
||||
if (
|
||||
!std::is_same<ExecutorType, rclcpp::experimental::executors::EventsExecutor>())
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
ExecutorType executor;
|
||||
|
||||
// Enable intra-process to guarantee deterministic and synchronous delivery of the message / event
|
||||
auto node_options = rclcpp::NodeOptions().use_intra_process_comms(true);
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node", node_options);
|
||||
|
||||
// Add node to the executor before creating the entities
|
||||
executor.add_node(node);
|
||||
|
||||
// Create entities, this will produce a notifier waitable event, telling the executor to refresh
|
||||
// the entities collection
|
||||
auto publisher = node->create_publisher<test_msgs::msg::Empty>("test_topic", rclcpp::QoS(10));
|
||||
size_t callback_count = 0;
|
||||
auto callback = [&callback_count](test_msgs::msg::Empty::ConstSharedPtr) {callback_count++;};
|
||||
auto subscription =
|
||||
node->create_subscription<test_msgs::msg::Empty>(
|
||||
"test_topic", rclcpp::QoS(10), std::move(callback));
|
||||
|
||||
ASSERT_EQ(callback_count, 0u);
|
||||
|
||||
// Publish a message so that the new entities (i.e. the subscriber) already have work to do
|
||||
publisher->publish(test_msgs::msg::Empty());
|
||||
|
||||
// NOTE: intra-process communication is enabled, so the subscription will immediately see
|
||||
// the new message, no risk of race conditions where spin_some gets called before the
|
||||
// message has been delivered.
|
||||
executor.spin_some();
|
||||
|
||||
// Verify that the callback is called as part of the spin above
|
||||
EXPECT_EQ(callback_count, 1u);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutorsWarmup, spin_some_doesnt_require_warmup_with_cbgroup)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// TODO(alsora): currently only the events-executor passes this test.
|
||||
// Enable single-threaded and multi-threaded executors
|
||||
// when https://github.com/ros2/rclcpp/pull/2595 gets merged
|
||||
if (
|
||||
!std::is_same<ExecutorType, rclcpp::experimental::executors::EventsExecutor>())
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
ExecutorType executor;
|
||||
|
||||
// Enable intra-process to guarantee deterministic and synchronous delivery of the message / event
|
||||
auto node_options = rclcpp::NodeOptions().use_intra_process_comms(true);
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node", node_options);
|
||||
|
||||
auto callback_group = node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
false);
|
||||
|
||||
// Add callback group to the executor before creating the entities
|
||||
executor.add_callback_group(callback_group, node->get_node_base_interface());
|
||||
|
||||
// Create entities, this will produce a notifier waitable event, telling the executor to refresh
|
||||
// the entities collection
|
||||
auto publisher = node->create_publisher<test_msgs::msg::Empty>("test_topic", rclcpp::QoS(10));
|
||||
size_t callback_count = 0;
|
||||
auto callback = [&callback_count](test_msgs::msg::Empty::ConstSharedPtr) {callback_count++;};
|
||||
rclcpp::SubscriptionOptions sub_options;
|
||||
sub_options.callback_group = callback_group;
|
||||
auto subscription =
|
||||
node->create_subscription<test_msgs::msg::Empty>(
|
||||
"test_topic", rclcpp::QoS(10), std::move(callback), sub_options);
|
||||
|
||||
ASSERT_EQ(callback_count, 0u);
|
||||
|
||||
// Publish a message so that the new entities (i.e. the subscriber) already have work to do
|
||||
publisher->publish(test_msgs::msg::Empty());
|
||||
|
||||
// NOTE: intra-process communication is enabled, so the subscription will immediately see
|
||||
// the new message, no risk of race conditions where spin_some gets called before the
|
||||
// message has been delivered.
|
||||
executor.spin_some();
|
||||
|
||||
// Verify that the callback is called as part of the spin above
|
||||
EXPECT_EQ(callback_count, 1u);
|
||||
}
|
||||
125
rclcpp/test/rclcpp/executors/test_waitable.cpp
Normal file
125
rclcpp/test/rclcpp/executors/test_waitable.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2024 Open Source Robotics Foundation, Inc.
|
||||
//
|
||||
// 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 <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "rclcpp/detail/add_guard_condition_to_rcl_wait_set.hpp"
|
||||
#include "rclcpp/waitable.hpp"
|
||||
|
||||
#include "rcl/wait.h"
|
||||
|
||||
#include "test_waitable.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
void
|
||||
TestWaitable::add_to_wait_set(rcl_wait_set_t & wait_set)
|
||||
{
|
||||
if (trigger_count_ > 0) {
|
||||
// Keep the gc triggered until the trigger count is reduced back to zero.
|
||||
// This is necessary if trigger() results in the wait set waking, but not
|
||||
// executing this waitable, in which case it needs to be re-triggered.
|
||||
gc_.trigger();
|
||||
}
|
||||
rclcpp::detail::add_guard_condition_to_rcl_wait_set(wait_set, gc_);
|
||||
}
|
||||
|
||||
void TestWaitable::trigger()
|
||||
{
|
||||
trigger_count_++;
|
||||
gc_.trigger();
|
||||
}
|
||||
|
||||
bool
|
||||
TestWaitable::is_ready(const rcl_wait_set_t & wait_set)
|
||||
{
|
||||
is_ready_count_++;
|
||||
for (size_t i = 0; i < wait_set.size_of_guard_conditions; ++i) {
|
||||
auto rcl_guard_condition = wait_set.guard_conditions[i];
|
||||
if (&gc_.get_rcl_guard_condition() == rcl_guard_condition) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
TestWaitable::take_data()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
TestWaitable::take_data_by_entity_id(size_t id)
|
||||
{
|
||||
(void) id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
TestWaitable::execute(const std::shared_ptr<void> &)
|
||||
{
|
||||
trigger_count_--;
|
||||
count_++;
|
||||
if (nullptr != on_execute_callback_) {
|
||||
on_execute_callback_();
|
||||
} else {
|
||||
// TODO(wjwwood): I don't know why this was here, but probably it should
|
||||
// not be there, or test cases where that is important should use the
|
||||
// on_execute_callback?
|
||||
std::this_thread::sleep_for(3ms);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TestWaitable::set_on_execute_callback(std::function<void()> on_execute_callback)
|
||||
{
|
||||
on_execute_callback_ = on_execute_callback;
|
||||
}
|
||||
|
||||
void
|
||||
TestWaitable::set_on_ready_callback(std::function<void(size_t, int)> callback)
|
||||
{
|
||||
auto gc_callback = [callback](size_t count) {
|
||||
callback(count, 0);
|
||||
};
|
||||
gc_.set_on_trigger_callback(gc_callback);
|
||||
}
|
||||
|
||||
void
|
||||
TestWaitable::clear_on_ready_callback()
|
||||
{
|
||||
gc_.set_on_trigger_callback(nullptr);
|
||||
}
|
||||
|
||||
size_t
|
||||
TestWaitable::get_number_of_ready_guard_conditions()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t
|
||||
TestWaitable::get_count() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
|
||||
size_t
|
||||
TestWaitable::get_is_ready_call_count() const
|
||||
{
|
||||
return is_ready_count_;
|
||||
}
|
||||
75
rclcpp/test/rclcpp/executors/test_waitable.hpp
Normal file
75
rclcpp/test/rclcpp/executors/test_waitable.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2024 Open Source Robotics Foundation, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef RCLCPP__EXECUTORS__TEST_WAITABLE_HPP_
|
||||
#define RCLCPP__EXECUTORS__TEST_WAITABLE_HPP_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "rclcpp/waitable.hpp"
|
||||
#include "rclcpp/guard_condition.hpp"
|
||||
|
||||
#include "rcl/wait.h"
|
||||
|
||||
class TestWaitable : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
TestWaitable() = default;
|
||||
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override;
|
||||
|
||||
void trigger();
|
||||
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & wait_set) override;
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override;
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override;
|
||||
|
||||
void
|
||||
execute(const std::shared_ptr<void> &) override;
|
||||
|
||||
void
|
||||
set_on_execute_callback(std::function<void()> on_execute_callback);
|
||||
|
||||
void
|
||||
set_on_ready_callback(std::function<void(size_t, int)> callback) override;
|
||||
|
||||
void
|
||||
clear_on_ready_callback() override;
|
||||
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override;
|
||||
|
||||
size_t
|
||||
get_count() const;
|
||||
|
||||
size_t
|
||||
get_is_ready_call_count() const;
|
||||
|
||||
private:
|
||||
std::atomic<size_t> trigger_count_ = 0;
|
||||
std::atomic<size_t> is_ready_count_ = 0;
|
||||
std::atomic<size_t> count_ = 0;
|
||||
rclcpp::GuardCondition gc_;
|
||||
std::function<void()> on_execute_callback_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // RCLCPP__EXECUTORS__TEST_WAITABLE_HPP_
|
||||
@@ -431,7 +431,7 @@ TYPED_TEST(TestAllClientTypesWithServer, client_qos)
|
||||
|
||||
rclcpp::ServicesQoS qos_profile;
|
||||
qos_profile.liveliness(rclcpp::LivelinessPolicy::Automatic);
|
||||
rclcpp::Duration duration(std::chrono::nanoseconds(1));
|
||||
rclcpp::Duration duration(std::chrono::milliseconds(1));
|
||||
qos_profile.deadline(duration);
|
||||
qos_profile.lifespan(duration);
|
||||
qos_profile.liveliness_lease_duration(duration);
|
||||
|
||||
@@ -93,3 +93,19 @@ TEST_F(TestCreateSubscription, create_with_statistics) {
|
||||
ASSERT_NE(nullptr, subscription);
|
||||
EXPECT_STREQ("/ns/topic_name", subscription->get_topic_name());
|
||||
}
|
||||
|
||||
TEST_F(TestCreateSubscription, create_with_intra_process_com) {
|
||||
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
|
||||
auto options = rclcpp::SubscriptionOptions();
|
||||
options.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
|
||||
auto callback = [](test_msgs::msg::Empty::ConstSharedPtr) {};
|
||||
rclcpp::Subscription<test_msgs::msg::Empty>::SharedPtr subscription;
|
||||
ASSERT_NO_THROW(
|
||||
{
|
||||
subscription = rclcpp::create_subscription<test_msgs::msg::Empty>(
|
||||
node, "topic_name", rclcpp::SystemDefaultsQoS(), callback, options);
|
||||
});
|
||||
ASSERT_NE(nullptr, subscription);
|
||||
EXPECT_STREQ("/ns/topic_name", subscription->get_topic_name());
|
||||
}
|
||||
|
||||
@@ -200,6 +200,57 @@ TEST(TestNodeOptions, copy) {
|
||||
rcl_arguments_get_count_unparsed(&other_rcl_options->arguments),
|
||||
rcl_arguments_get_count_unparsed(&rcl_options->arguments));
|
||||
}
|
||||
|
||||
{
|
||||
// The following scope test is missing:
|
||||
// "arguments" because it is already tested in the above scopes
|
||||
// "parameter_event_publisher_options" because it can not be directly compared with EXPECT_EQ
|
||||
// "allocator" because it can not be directly compared with EXPECT_EQ
|
||||
|
||||
// We separate attribute modification from variable initialisation (copy assignment operator)
|
||||
// to be sure the "non_default_options"'s properties are correctly set before testing the
|
||||
// assignment operator.
|
||||
auto non_default_options = rclcpp::NodeOptions();
|
||||
non_default_options
|
||||
.parameter_overrides({rclcpp::Parameter("foo", 0), rclcpp::Parameter("bar", "1")})
|
||||
.use_global_arguments(false)
|
||||
.enable_rosout(false)
|
||||
.use_intra_process_comms(true)
|
||||
.enable_topic_statistics(true)
|
||||
.start_parameter_services(false)
|
||||
.enable_logger_service(true)
|
||||
.start_parameter_event_publisher(false)
|
||||
.clock_type(RCL_SYSTEM_TIME)
|
||||
.clock_qos(rclcpp::SensorDataQoS())
|
||||
.use_clock_thread(false)
|
||||
.parameter_event_qos(rclcpp::ClockQoS())
|
||||
.rosout_qos(rclcpp::ParameterEventsQoS())
|
||||
.allow_undeclared_parameters(true)
|
||||
.automatically_declare_parameters_from_overrides(true);
|
||||
|
||||
auto copied_options = non_default_options;
|
||||
EXPECT_EQ(non_default_options.parameter_overrides(), copied_options.parameter_overrides());
|
||||
EXPECT_EQ(non_default_options.use_global_arguments(), copied_options.use_global_arguments());
|
||||
EXPECT_EQ(non_default_options.enable_rosout(), copied_options.enable_rosout());
|
||||
EXPECT_EQ(non_default_options.use_intra_process_comms(),
|
||||
copied_options.use_intra_process_comms());
|
||||
EXPECT_EQ(non_default_options.enable_topic_statistics(),
|
||||
copied_options.enable_topic_statistics());
|
||||
EXPECT_EQ(non_default_options.start_parameter_services(),
|
||||
copied_options.start_parameter_services());
|
||||
EXPECT_EQ(non_default_options.enable_logger_service(), copied_options.enable_logger_service());
|
||||
EXPECT_EQ(non_default_options.start_parameter_event_publisher(),
|
||||
copied_options.start_parameter_event_publisher());
|
||||
EXPECT_EQ(non_default_options.clock_type(), copied_options.clock_type());
|
||||
EXPECT_EQ(non_default_options.clock_qos(), copied_options.clock_qos());
|
||||
EXPECT_EQ(non_default_options.use_clock_thread(), copied_options.use_clock_thread());
|
||||
EXPECT_EQ(non_default_options.parameter_event_qos(), copied_options.parameter_event_qos());
|
||||
EXPECT_EQ(non_default_options.rosout_qos(), copied_options.rosout_qos());
|
||||
EXPECT_EQ(non_default_options.allow_undeclared_parameters(),
|
||||
copied_options.allow_undeclared_parameters());
|
||||
EXPECT_EQ(non_default_options.automatically_declare_parameters_from_overrides(),
|
||||
copied_options.automatically_declare_parameters_from_overrides());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestNodeOptions, append_parameter_override) {
|
||||
|
||||
@@ -178,6 +178,21 @@ TEST_F(TestPublisher, various_creation_signatures) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Testing publisher with intraprocess enabled and SystemDefaultQoS
|
||||
*/
|
||||
TEST_F(TestPublisher, test_publisher_with_system_default_qos) {
|
||||
initialize(rclcpp::NodeOptions().use_intra_process_comms(false));
|
||||
// explicitly enable intra-process comm with publisher option
|
||||
auto options = rclcpp::PublisherOptions();
|
||||
options.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
using test_msgs::msg::Empty;
|
||||
ASSERT_NO_THROW(
|
||||
{
|
||||
auto publisher = node->create_publisher<Empty>("topic", rclcpp::SystemDefaultsQoS());
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Testing publisher with intraprocess enabled and invalid QoS
|
||||
*/
|
||||
@@ -423,11 +438,10 @@ TEST_F(TestPublisher, intra_process_publish_failures) {
|
||||
publisher->get_publisher_handle().get(), msg.get()));
|
||||
}
|
||||
}
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
node->create_publisher<test_msgs::msg::Empty>(
|
||||
"topic", rclcpp::QoS(0), options),
|
||||
std::invalid_argument(
|
||||
"intraprocess communication is not allowed with a zero qos history depth value"));
|
||||
// a zero depth with KEEP_LAST doesn't make sense,
|
||||
// this will be interpreted as SystemDefaultQoS by rclcpp.
|
||||
EXPECT_NO_THROW(
|
||||
node->create_publisher<test_msgs::msg::Empty>("topic", rclcpp::QoS(0), options));
|
||||
}
|
||||
|
||||
TEST_F(TestPublisher, inter_process_publish_failures) {
|
||||
|
||||
@@ -123,7 +123,7 @@ TEST_F(TestServiceSub, construction_and_destruction) {
|
||||
{
|
||||
ASSERT_THROW(
|
||||
{
|
||||
auto service = node->create_service<rcl_interfaces::srv::ListParameters>(
|
||||
auto service = subnode->create_service<rcl_interfaces::srv::ListParameters>(
|
||||
"invalid_service?", callback);
|
||||
}, rclcpp::exceptions::InvalidServiceNameError);
|
||||
}
|
||||
@@ -335,7 +335,7 @@ TEST_F(TestService, rcl_service_request_subscription_get_actual_qos_error) {
|
||||
TEST_F(TestService, server_qos) {
|
||||
rclcpp::ServicesQoS qos_profile;
|
||||
qos_profile.liveliness(rclcpp::LivelinessPolicy::Automatic);
|
||||
rclcpp::Duration duration(std::chrono::nanoseconds(1));
|
||||
rclcpp::Duration duration(std::chrono::milliseconds(1));
|
||||
qos_profile.deadline(duration);
|
||||
qos_profile.lifespan(duration);
|
||||
qos_profile.liveliness_lease_duration(duration);
|
||||
|
||||
@@ -92,9 +92,16 @@ TEST_F(TestServiceIntrospection, service_introspection_nominal)
|
||||
request->set__int64_value(42);
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
size_t tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
auto future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -149,9 +156,11 @@ TEST_F(TestServiceIntrospection, service_introspection_nominal)
|
||||
TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
{
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
|
||||
ASSERT_EQ(sub->get_publisher_count(), 0);
|
||||
|
||||
auto request = std::make_shared<BasicTypes::Request>();
|
||||
request->set__bool_value(true);
|
||||
@@ -169,9 +178,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
size_t tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 1 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 1u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -186,9 +202,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 1 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 1u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -203,9 +226,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -221,9 +251,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_content)
|
||||
{
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
size_t tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
auto request = std::make_shared<BasicTypes::Request>();
|
||||
request->set__bool_value(true);
|
||||
@@ -245,9 +282,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_cont
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -278,9 +322,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_cont
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -311,9 +362,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_cont
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
|
||||
@@ -60,7 +60,7 @@ TEST_F(TestSubscriptionOptions, topic_statistics_options_default_and_set) {
|
||||
EXPECT_EQ(options.topic_stats_options.state, rclcpp::TopicStatisticsState::NodeDefault);
|
||||
EXPECT_EQ(options.topic_stats_options.publish_topic, defaultPublishTopic);
|
||||
EXPECT_EQ(options.topic_stats_options.publish_period, 1s);
|
||||
EXPECT_EQ(options.topic_stats_options.qos, rclcpp::SystemDefaultsQoS());
|
||||
EXPECT_EQ(options.topic_stats_options.qos, rclcpp::SystemDefaultsQoS().keep_last(10));
|
||||
|
||||
options.topic_stats_options.state = rclcpp::TopicStatisticsState::Enable;
|
||||
options.topic_stats_options.publish_topic = "topic_statistics";
|
||||
|
||||
@@ -3,6 +3,15 @@ Changelog for package rclcpp_action
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
28.1.6 (2024-12-18)
|
||||
-------------------
|
||||
|
||||
28.1.5 (2024-09-19)
|
||||
-------------------
|
||||
|
||||
28.1.4 (2024-09-06)
|
||||
-------------------
|
||||
|
||||
28.1.3 (2024-06-27)
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="2">
|
||||
<name>rclcpp_action</name>
|
||||
<version>28.1.3</version>
|
||||
<version>28.1.6</version>
|
||||
<description>Adds action APIs for C++.</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
Changelog for package rclcpp_components
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
28.1.6 (2024-12-18)
|
||||
-------------------
|
||||
|
||||
28.1.5 (2024-09-19)
|
||||
-------------------
|
||||
|
||||
28.1.4 (2024-09-06)
|
||||
-------------------
|
||||
|
||||
28.1.3 (2024-06-27)
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="2">
|
||||
<name>rclcpp_components</name>
|
||||
<version>28.1.3</version>
|
||||
<version>28.1.6</version>
|
||||
<description>Package containing tools for dynamically loadable components</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -3,6 +3,15 @@ Changelog for package rclcpp_lifecycle
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
28.1.6 (2024-12-18)
|
||||
-------------------
|
||||
|
||||
28.1.5 (2024-09-19)
|
||||
-------------------
|
||||
|
||||
28.1.4 (2024-09-06)
|
||||
-------------------
|
||||
|
||||
28.1.3 (2024-06-27)
|
||||
-------------------
|
||||
* Revert "call shutdown in LifecycleNode dtor to avoid leaving the device in unknown state (2nd) (backport `#2528 <https://github.com/ros2/rclcpp/issues/2528>`_) (`#2542 <https://github.com/ros2/rclcpp/issues/2542>`_)" (`#2558 <https://github.com/ros2/rclcpp/issues/2558>`_)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="2">
|
||||
<name>rclcpp_lifecycle</name>
|
||||
<version>28.1.3</version>
|
||||
<version>28.1.6</version>
|
||||
<description>Package containing a prototype for lifecycle implementation</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
Reference in New Issue
Block a user