Compare commits
34 Commits
native_buf
...
28.1.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
985e320de3 | ||
|
|
eac2e8a7dd | ||
|
|
cff2711aba | ||
|
|
608e2f2e56 | ||
|
|
bd695f4a3b | ||
|
|
74162b19bf | ||
|
|
e217532817 | ||
|
|
a7b8ada6a7 | ||
|
|
df09c6e77d | ||
|
|
06ae92ae96 | ||
|
|
99b3803ce2 | ||
|
|
bbe2b99893 | ||
|
|
09e5dd70f4 | ||
|
|
5551facd9b | ||
|
|
38ed9ed172 | ||
|
|
fad351ff85 | ||
|
|
08c6cd9f9a | ||
|
|
830a1f0212 | ||
|
|
1b4485443d | ||
|
|
d73fc2a86a | ||
|
|
e083bf9b7d | ||
|
|
c3bc232eae | ||
|
|
7f5b44ebe2 | ||
|
|
8ab10aabc0 | ||
|
|
119a7bcbac | ||
|
|
0beba97156 | ||
|
|
c88a3602ab | ||
|
|
258b0a06bb | ||
|
|
bf6a6733a1 | ||
|
|
4f17dee383 | ||
|
|
2c23e3a73a | ||
|
|
8934b5a0a9 | ||
|
|
3407564a15 | ||
|
|
8b3be2ad7e |
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,172 @@
|
||||
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>`_)
|
||||
Co-authored-by: Mario Domínguez López <116071334+Mario-DL@users.noreply.github.com>
|
||||
(cherry picked from commit 7c096888caf92aa7557e1d3efc5448b56d8ce81c)
|
||||
Co-authored-by: Alejandro Hernández Cordero <ahcorde@gmail.com>
|
||||
* Contributors: mergify[bot]
|
||||
|
||||
28.1.2 (2024-05-13)
|
||||
-------------------
|
||||
* add impl pointer for ExecutorOptions (`#2523 <https://github.com/ros2/rclcpp/issues/2523>`_) (`#2525 <https://github.com/ros2/rclcpp/issues/2525>`_)
|
||||
* add impl pointer for ExecutorOptions
|
||||
(cherry picked from commit 343b29b617b163ad72b9fe3f6441dd4ed3d3af09)
|
||||
Co-authored-by: William Woodall <william@osrfoundation.org>
|
||||
* Fixup Executor::spin_all() regression fix (`#2517 <https://github.com/ros2/rclcpp/issues/2517>`_) (`#2521 <https://github.com/ros2/rclcpp/issues/2521>`_)
|
||||
* test(Executors): Added tests for busy waiting
|
||||
Checks if executors are busy waiting while they should block
|
||||
in spin_some or spin_all.
|
||||
* fix: Reworked spinAll test
|
||||
This test was strange. It looked like, it assumed that spin_all did
|
||||
not return instantly. Also it was racy, as the thread could terminate
|
||||
instantly.
|
||||
* fix(Executor): Fixed spin_all not returning instantly is no work was available
|
||||
* Update rclcpp/test/rclcpp/executors/test_executors.cpp
|
||||
* test(executors): Added test for busy waiting while calling spin
|
||||
* fix(executor): Reset wait_result on every call to spin_some_impl
|
||||
Before, the method would not recollect available work in case of
|
||||
spin_some, spin_all. This would lead to the method behaving differently
|
||||
than to what the documentation states.
|
||||
* restore previous test logic for now
|
||||
* refactor spin_some_impl's logic and improve busy wait tests
|
||||
* added some more comments about the implementation
|
||||
---------
|
||||
Co-authored-by: Janosch Machowinski <J.Machowinski@cellumation.com>
|
||||
Co-authored-by: jmachowinski <jmachowinski@users.noreply.github.com>
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
Co-authored-by: William Woodall <william@osrfoundation.org>
|
||||
* Contributors: mergify[bot]
|
||||
|
||||
28.1.1 (2024-04-24)
|
||||
-------------------
|
||||
* Revise the description of service configure_introspection() (`#2511 <https://github.com/ros2/rclcpp/issues/2511>`_) (`#2513 <https://github.com/ros2/rclcpp/issues/2513>`_)
|
||||
* Contributors: mergify[bot]
|
||||
|
||||
28.1.0 (2024-04-16)
|
||||
-------------------
|
||||
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
|
||||
|
||||
@@ -63,6 +63,7 @@ set(${PROJECT_NAME}_SRCS
|
||||
src/rclcpp/exceptions/exceptions.cpp
|
||||
src/rclcpp/executable_list.cpp
|
||||
src/rclcpp/executor.cpp
|
||||
src/rclcpp/executor_options.cpp
|
||||
src/rclcpp/executors.cpp
|
||||
src/rclcpp/executors/executor_entities_collection.cpp
|
||||
src/rclcpp/executors/executor_entities_collector.cpp
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#ifndef RCLCPP__EXECUTOR_OPTIONS_HPP_
|
||||
#define RCLCPP__EXECUTOR_OPTIONS_HPP_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "rclcpp/context.hpp"
|
||||
#include "rclcpp/contexts/default_context.hpp"
|
||||
#include "rclcpp/memory_strategies.hpp"
|
||||
@@ -24,18 +26,30 @@
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
class ExecutorOptionsImplementation;
|
||||
|
||||
/// Options to be passed to the executor constructor.
|
||||
struct ExecutorOptions
|
||||
{
|
||||
ExecutorOptions()
|
||||
: memory_strategy(rclcpp::memory_strategies::create_default_strategy()),
|
||||
context(rclcpp::contexts::get_global_default_context()),
|
||||
max_conditions(0)
|
||||
{}
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorOptions();
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
virtual ~ExecutorOptions();
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorOptions(const ExecutorOptions &);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorOptions & operator=(const ExecutorOptions &);
|
||||
|
||||
rclcpp::memory_strategy::MemoryStrategy::SharedPtr memory_strategy;
|
||||
rclcpp::Context::SharedPtr context;
|
||||
size_t max_conditions;
|
||||
|
||||
private:
|
||||
/// Pointer to implementation
|
||||
std::unique_ptr<ExecutorOptionsImplementation> impl_;
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -499,7 +499,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure client introspection.
|
||||
/// Configure service introspection.
|
||||
/**
|
||||
* \param[in] clock clock to use to generate introspection timestamps
|
||||
* \param[in] qos_service_event_pub QoS settings to use when creating the introspection publisher
|
||||
|
||||
@@ -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.0</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,26 +364,54 @@ 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
|
||||
// both spin_some and spin_all wait for work at the beginning
|
||||
wait_result_.reset();
|
||||
wait_for_work(std::chrono::milliseconds(0));
|
||||
bool just_waited = true;
|
||||
|
||||
// The logic of this while loop is as follows:
|
||||
//
|
||||
// - while not shutdown, and spinning (not canceled), and not max duration reached...
|
||||
// - try to get an executable item to execute, and execute it if available
|
||||
// - otherwise, reset the wait result, and ...
|
||||
// - if there was no work available just after waiting, break the loop unconditionally
|
||||
// - this is appropriate for both spin_some and spin_all which use this function
|
||||
// - else if exhaustive = true, then wait for work again
|
||||
// - this is only used for spin_all and not spin_some
|
||||
// - else break
|
||||
// - this only occurs with spin_some
|
||||
//
|
||||
// The logic of this loop is subtle and should be carefully changed if at all.
|
||||
// See also:
|
||||
// https://github.com/ros2/rclcpp/issues/2508
|
||||
// https://github.com/ros2/rclcpp/pull/2517
|
||||
while (rclcpp::ok(context_) && spinning.load() && max_duration_not_elapsed()) {
|
||||
if (!wait_result_.has_value()) {
|
||||
wait_for_work(std::chrono::milliseconds(0));
|
||||
}
|
||||
|
||||
AnyExecutable any_exec;
|
||||
if (get_next_ready_executable(any_exec)) {
|
||||
execute_any_executable(any_exec);
|
||||
just_waited = false;
|
||||
} else {
|
||||
// If nothing is ready, reset the result to signal we are
|
||||
// ready to wait again
|
||||
// if nothing is ready, reset the result to clear it
|
||||
wait_result_.reset();
|
||||
}
|
||||
|
||||
if (!wait_result_.has_value() && !exhaustive) {
|
||||
// In the case of spin some, then we can exit
|
||||
// In the case of spin all, then we will allow ourselves to wait again.
|
||||
break;
|
||||
if (just_waited) {
|
||||
// there was no work after just waiting, always exit in this case
|
||||
// before the exhaustive condition can be checked
|
||||
break;
|
||||
}
|
||||
|
||||
if (exhaustive) {
|
||||
// if exhaustive, wait for work again
|
||||
// this only happens for spin_all; spin_some only waits at the start
|
||||
wait_for_work(std::chrono::milliseconds(0));
|
||||
just_waited = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,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);
|
||||
}
|
||||
|
||||
@@ -701,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
rclcpp/src/rclcpp/executor_options.cpp
Normal file
55
rclcpp/src/rclcpp/executor_options.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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 "rclcpp/executor_options.hpp"
|
||||
|
||||
using rclcpp::ExecutorOptions;
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
class ExecutorOptionsImplementation {};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
ExecutorOptions::ExecutorOptions()
|
||||
: memory_strategy(rclcpp::memory_strategies::create_default_strategy()),
|
||||
context(rclcpp::contexts::get_global_default_context()),
|
||||
max_conditions(0),
|
||||
impl_(nullptr)
|
||||
{}
|
||||
|
||||
ExecutorOptions::~ExecutorOptions()
|
||||
{}
|
||||
|
||||
ExecutorOptions::ExecutorOptions(const ExecutorOptions & other)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
ExecutorOptions & ExecutorOptions::operator=(const ExecutorOptions & other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
this->memory_strategy = other.memory_strategy;
|
||||
this->context = other.context;
|
||||
this->max_conditions = other.max_conditions;
|
||||
if (nullptr != other.impl_) {
|
||||
this->impl_ = std::make_unique<ExecutorOptionsImplementation>(*other.impl_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -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,106 +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
|
||||
{
|
||||
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_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<size_t> trigger_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;
|
||||
@@ -486,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;
|
||||
|
||||
@@ -578,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;
|
||||
|
||||
@@ -745,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;
|
||||
@@ -869,3 +764,60 @@ TEST(TestExecutors, testSpinWithNonDefaultContext)
|
||||
|
||||
rclcpp::shutdown(non_default_context);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, releaseOwnershipEntityAfterSpinningCancel)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
auto future = std::async(std::launch::async, [&executor] {executor.spin();});
|
||||
|
||||
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);
|
||||
|
||||
EXPECT_EQ(server.use_count(), 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, testRaceDropCallbackGroupFromSecondThread)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// Create an executor
|
||||
ExecutorType executor;
|
||||
executor.add_node(this->node);
|
||||
|
||||
// Start spinning
|
||||
auto executor_thread = std::thread(
|
||||
[&executor]() {
|
||||
executor.spin();
|
||||
});
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
@@ -310,3 +310,25 @@ TEST_F(CLASSNAME(TestContentFilterSubscription, RMW_IMPLEMENTATION), content_fil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
CLASSNAME(
|
||||
TestContentFilterSubscription,
|
||||
RMW_IMPLEMENTATION), create_two_content_filters_with_same_topic_name_and_destroy) {
|
||||
|
||||
// Create another content filter
|
||||
auto options = rclcpp::SubscriptionOptions();
|
||||
|
||||
std::string filter_expression = "int32_value > %0";
|
||||
std::vector<std::string> expression_parameters = {"4"};
|
||||
|
||||
options.content_filter_options.filter_expression = filter_expression;
|
||||
options.content_filter_options.expression_parameters = expression_parameters;
|
||||
|
||||
auto callback = [](std::shared_ptr<const test_msgs::msg::BasicTypes>) {};
|
||||
auto sub_2 = node->create_subscription<test_msgs::msg::BasicTypes>(
|
||||
"content_filter_topic", qos, callback, options);
|
||||
|
||||
EXPECT_NE(nullptr, sub_2);
|
||||
sub_2.reset();
|
||||
}
|
||||
|
||||
@@ -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,24 @@ 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)
|
||||
-------------------
|
||||
|
||||
28.1.2 (2024-05-13)
|
||||
-------------------
|
||||
|
||||
28.1.1 (2024-04-24)
|
||||
-------------------
|
||||
|
||||
28.1.0 (2024-04-16)
|
||||
-------------------
|
||||
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
|
||||
|
||||
@@ -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.0</version>
|
||||
<version>28.1.6</version>
|
||||
<description>Adds action APIs for C++.</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -2,6 +2,24 @@
|
||||
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)
|
||||
-------------------
|
||||
|
||||
28.1.2 (2024-05-13)
|
||||
-------------------
|
||||
|
||||
28.1.1 (2024-04-24)
|
||||
-------------------
|
||||
|
||||
28.1.0 (2024-04-16)
|
||||
-------------------
|
||||
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
|
||||
|
||||
@@ -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.0</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,35 @@ 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>`_)
|
||||
* 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>`_)
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
* rclcpp::shutdown should not be called before LifecycleNode dtor. (`#2527 <https://github.com/ros2/rclcpp/issues/2527>`_) (`#2540 <https://github.com/ros2/rclcpp/issues/2540>`_)
|
||||
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
||||
* Contributors: Tomoya Fujita, mergify[bot]
|
||||
|
||||
28.1.2 (2024-05-13)
|
||||
-------------------
|
||||
* Revert "call shutdown in LifecycleNode dtor to avoid leaving the device in un… (`#2450 <https://github.com/ros2/rclcpp/issues/2450>`_)" (`#2522 <https://github.com/ros2/rclcpp/issues/2522>`_) (`#2524 <https://github.com/ros2/rclcpp/issues/2524>`_)
|
||||
This reverts commit 04ea0bb00293387791522590b7347a2282cda290.
|
||||
(cherry picked from commit 42b0b5775b4e68718c5949308c9e1a059930ded7)
|
||||
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
|
||||
* Contributors: mergify[bot]
|
||||
|
||||
28.1.1 (2024-04-24)
|
||||
-------------------
|
||||
|
||||
28.1.0 (2024-04-16)
|
||||
-------------------
|
||||
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
|
||||
|
||||
@@ -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.0</version>
|
||||
<version>28.1.6</version>
|
||||
<description>Package containing a prototype for lifecycle implementation</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -152,22 +152,6 @@ LifecycleNode::LifecycleNode(
|
||||
|
||||
LifecycleNode::~LifecycleNode()
|
||||
{
|
||||
// shutdown if necessary to avoid leaving the device in unknown state
|
||||
if (LifecycleNode::get_current_state().id() !=
|
||||
lifecycle_msgs::msg::State::PRIMARY_STATE_FINALIZED)
|
||||
{
|
||||
auto ret = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::ERROR;
|
||||
auto finalized = LifecycleNode::shutdown(ret);
|
||||
if (finalized.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_FINALIZED ||
|
||||
ret != rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS)
|
||||
{
|
||||
RCLCPP_WARN(
|
||||
rclcpp::get_logger("rclcpp_lifecycle"),
|
||||
"Shutdown error in destruction of LifecycleNode: final state(%s)",
|
||||
finalized.label().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// release sub-interfaces in an order that allows them to consult with node_base during tear-down
|
||||
node_waitables_.reset();
|
||||
node_time_source_.reset();
|
||||
|
||||
@@ -447,146 +447,6 @@ TEST_F(TestDefaultStateMachine, bad_mood) {
|
||||
EXPECT_EQ(1u, test_node->number_of_callbacks);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(TestDefaultStateMachine, shutdown_from_each_primary_state) {
|
||||
auto success = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
|
||||
auto reset_key = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::ERROR;
|
||||
|
||||
// PRIMARY_STATE_UNCONFIGURED to shutdown
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<EmptyLifecycleNode>("testnode");
|
||||
auto finalized = test_node->shutdown(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(finalized.id(), State::PRIMARY_STATE_FINALIZED);
|
||||
}
|
||||
|
||||
// PRIMARY_STATE_INACTIVE to shutdown
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<EmptyLifecycleNode>("testnode");
|
||||
auto configured = test_node->configure(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(configured.id(), State::PRIMARY_STATE_INACTIVE);
|
||||
ret = reset_key;
|
||||
auto finalized = test_node->shutdown(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(finalized.id(), State::PRIMARY_STATE_FINALIZED);
|
||||
}
|
||||
|
||||
// PRIMARY_STATE_ACTIVE to shutdown
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<EmptyLifecycleNode>("testnode");
|
||||
auto configured = test_node->configure(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(configured.id(), State::PRIMARY_STATE_INACTIVE);
|
||||
ret = reset_key;
|
||||
auto activated = test_node->activate(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(activated.id(), State::PRIMARY_STATE_ACTIVE);
|
||||
ret = reset_key;
|
||||
auto finalized = test_node->shutdown(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(finalized.id(), State::PRIMARY_STATE_FINALIZED);
|
||||
}
|
||||
|
||||
// PRIMARY_STATE_FINALIZED to shutdown
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<EmptyLifecycleNode>("testnode");
|
||||
auto configured = test_node->configure(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(configured.id(), State::PRIMARY_STATE_INACTIVE);
|
||||
ret = reset_key;
|
||||
auto activated = test_node->activate(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(activated.id(), State::PRIMARY_STATE_ACTIVE);
|
||||
ret = reset_key;
|
||||
auto finalized = test_node->shutdown(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(finalized.id(), State::PRIMARY_STATE_FINALIZED);
|
||||
ret = reset_key;
|
||||
auto finalized_again = test_node->shutdown(ret);
|
||||
EXPECT_EQ(reset_key, ret);
|
||||
EXPECT_EQ(finalized_again.id(), State::PRIMARY_STATE_FINALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestDefaultStateMachine, test_shutdown_on_dtor) {
|
||||
auto success = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
|
||||
auto reset_key = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::ERROR;
|
||||
|
||||
bool shutdown_cb_called = false;
|
||||
auto on_shutdown_callback =
|
||||
[&shutdown_cb_called](const rclcpp_lifecycle::State &) ->
|
||||
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn {
|
||||
shutdown_cb_called = true;
|
||||
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
|
||||
};
|
||||
|
||||
// PRIMARY_STATE_UNCONFIGURED to shutdown via dtor
|
||||
shutdown_cb_called = false;
|
||||
{
|
||||
auto test_node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testnode");
|
||||
test_node->register_on_shutdown(std::bind(on_shutdown_callback, std::placeholders::_1));
|
||||
EXPECT_EQ(State::PRIMARY_STATE_UNCONFIGURED, test_node->get_current_state().id());
|
||||
EXPECT_FALSE(shutdown_cb_called);
|
||||
}
|
||||
EXPECT_TRUE(shutdown_cb_called);
|
||||
|
||||
// PRIMARY_STATE_INACTIVE to shutdown via dtor
|
||||
shutdown_cb_called = false;
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testnode");
|
||||
test_node->register_on_shutdown(std::bind(on_shutdown_callback, std::placeholders::_1));
|
||||
auto configured = test_node->configure(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(configured.id(), State::PRIMARY_STATE_INACTIVE);
|
||||
EXPECT_FALSE(shutdown_cb_called);
|
||||
}
|
||||
EXPECT_TRUE(shutdown_cb_called);
|
||||
|
||||
// PRIMARY_STATE_ACTIVE to shutdown via dtor
|
||||
shutdown_cb_called = false;
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testnode");
|
||||
test_node->register_on_shutdown(std::bind(on_shutdown_callback, std::placeholders::_1));
|
||||
auto configured = test_node->configure(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(configured.id(), State::PRIMARY_STATE_INACTIVE);
|
||||
ret = reset_key;
|
||||
auto activated = test_node->activate(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(activated.id(), State::PRIMARY_STATE_ACTIVE);
|
||||
EXPECT_FALSE(shutdown_cb_called);
|
||||
}
|
||||
EXPECT_TRUE(shutdown_cb_called);
|
||||
|
||||
// PRIMARY_STATE_FINALIZED to shutdown via dtor
|
||||
shutdown_cb_called = false;
|
||||
{
|
||||
auto ret = reset_key;
|
||||
auto test_node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testnode");
|
||||
test_node->register_on_shutdown(std::bind(on_shutdown_callback, std::placeholders::_1));
|
||||
auto configured = test_node->configure(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(configured.id(), State::PRIMARY_STATE_INACTIVE);
|
||||
ret = reset_key;
|
||||
auto activated = test_node->activate(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(activated.id(), State::PRIMARY_STATE_ACTIVE);
|
||||
ret = reset_key;
|
||||
auto finalized = test_node->shutdown(ret);
|
||||
EXPECT_EQ(success, ret);
|
||||
EXPECT_EQ(finalized.id(), State::PRIMARY_STATE_FINALIZED);
|
||||
EXPECT_TRUE(shutdown_cb_called); // should be called already
|
||||
}
|
||||
EXPECT_TRUE(shutdown_cb_called);
|
||||
}
|
||||
|
||||
TEST_F(TestDefaultStateMachine, lifecycle_subscriber) {
|
||||
auto test_node = std::make_shared<MoodyLifecycleNode<GoodMood>>("testnode");
|
||||
|
||||
|
||||
@@ -55,12 +55,6 @@ public:
|
||||
explicit EmptyLifecycleNode(const std::string & node_name, const TimerType & timer_type)
|
||||
: rclcpp_lifecycle::LifecycleNode(node_name)
|
||||
{
|
||||
rclcpp::PublisherOptionsWithAllocator<std::allocator<void>> options;
|
||||
publisher_ =
|
||||
std::make_shared<rclcpp_lifecycle::LifecyclePublisher<test_msgs::msg::Empty>>(
|
||||
get_node_base_interface().get(), std::string("topic"), rclcpp::QoS(10), options);
|
||||
add_managed_entity(publisher_);
|
||||
|
||||
// For coverage this is being added here
|
||||
switch (timer_type) {
|
||||
case TimerType::WALL_TIMER:
|
||||
@@ -77,14 +71,6 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<test_msgs::msg::Empty>> publisher()
|
||||
{
|
||||
return publisher_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<test_msgs::msg::Empty>> publisher_;
|
||||
};
|
||||
|
||||
class TestLifecyclePublisher : public ::testing::TestWithParam<TimerType>
|
||||
@@ -93,95 +79,103 @@ public:
|
||||
void SetUp()
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
node_ = std::make_shared<EmptyLifecycleNode>("node", GetParam());
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<EmptyLifecycleNode> node_;
|
||||
};
|
||||
|
||||
TEST_P(TestLifecyclePublisher, publish_managed_by_node) {
|
||||
auto node = std::make_shared<EmptyLifecycleNode>("node", GetParam());
|
||||
|
||||
rclcpp::PublisherOptionsWithAllocator<std::allocator<void>> options;
|
||||
std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<test_msgs::msg::Empty>> publisher =
|
||||
node->create_publisher<test_msgs::msg::Empty>(std::string("topic"), rclcpp::QoS(10), options);
|
||||
|
||||
// transition via LifecycleNode
|
||||
auto success = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
|
||||
auto reset_key = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::ERROR;
|
||||
auto ret = reset_key;
|
||||
|
||||
EXPECT_EQ(State::PRIMARY_STATE_UNCONFIGURED, node_->get_current_state().id());
|
||||
node_->trigger_transition(
|
||||
EXPECT_EQ(State::PRIMARY_STATE_UNCONFIGURED, node->get_current_state().id());
|
||||
node->trigger_transition(
|
||||
rclcpp_lifecycle::Transition(Transition::TRANSITION_CONFIGURE), ret);
|
||||
ASSERT_EQ(success, ret);
|
||||
ret = reset_key;
|
||||
node_->trigger_transition(
|
||||
node->trigger_transition(
|
||||
rclcpp_lifecycle::Transition(Transition::TRANSITION_ACTIVATE), ret);
|
||||
ASSERT_EQ(success, ret);
|
||||
ret = reset_key;
|
||||
EXPECT_TRUE(node_->publisher()->is_activated());
|
||||
EXPECT_TRUE(publisher->is_activated());
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(*msg_ptr));
|
||||
EXPECT_NO_THROW(publisher->publish(*msg_ptr));
|
||||
}
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(msg_ptr)));
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(msg_ptr)));
|
||||
}
|
||||
{
|
||||
auto loaned_msg = node_->publisher()->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(loaned_msg)));
|
||||
auto loaned_msg = publisher->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(loaned_msg)));
|
||||
}
|
||||
node_->trigger_transition(
|
||||
node->trigger_transition(
|
||||
rclcpp_lifecycle::Transition(Transition::TRANSITION_DEACTIVATE), ret);
|
||||
ASSERT_EQ(success, ret);
|
||||
ret = reset_key;
|
||||
(void)ret; // Just to make clang happy
|
||||
EXPECT_FALSE(node_->publisher()->is_activated());
|
||||
EXPECT_FALSE(publisher->is_activated());
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(*msg_ptr));
|
||||
EXPECT_NO_THROW(publisher->publish(*msg_ptr));
|
||||
}
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(msg_ptr)));
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(msg_ptr)));
|
||||
}
|
||||
{
|
||||
auto loaned_msg = node_->publisher()->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(loaned_msg)));
|
||||
auto loaned_msg = publisher->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(loaned_msg)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(TestLifecyclePublisher, publish) {
|
||||
auto node = std::make_shared<EmptyLifecycleNode>("node", GetParam());
|
||||
|
||||
rclcpp::PublisherOptionsWithAllocator<std::allocator<void>> options;
|
||||
std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<test_msgs::msg::Empty>> publisher =
|
||||
node->create_publisher<test_msgs::msg::Empty>(std::string("topic"), rclcpp::QoS(10), options);
|
||||
|
||||
// transition via LifecyclePublisher
|
||||
node_->publisher()->on_deactivate();
|
||||
EXPECT_FALSE(node_->publisher()->is_activated());
|
||||
publisher->on_deactivate();
|
||||
EXPECT_FALSE(publisher->is_activated());
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(*msg_ptr));
|
||||
EXPECT_NO_THROW(publisher->publish(*msg_ptr));
|
||||
}
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(msg_ptr)));
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(msg_ptr)));
|
||||
}
|
||||
{
|
||||
auto loaned_msg = node_->publisher()->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(loaned_msg)));
|
||||
auto loaned_msg = publisher->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(loaned_msg)));
|
||||
}
|
||||
node_->publisher()->on_activate();
|
||||
EXPECT_TRUE(node_->publisher()->is_activated());
|
||||
publisher->on_activate();
|
||||
EXPECT_TRUE(publisher->is_activated());
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(*msg_ptr));
|
||||
EXPECT_NO_THROW(publisher->publish(*msg_ptr));
|
||||
}
|
||||
{
|
||||
auto msg_ptr = std::make_unique<test_msgs::msg::Empty>();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(msg_ptr)));
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(msg_ptr)));
|
||||
}
|
||||
{
|
||||
auto loaned_msg = node_->publisher()->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(node_->publisher()->publish(std::move(loaned_msg)));
|
||||
auto loaned_msg = publisher->borrow_loaned_message();
|
||||
EXPECT_NO_THROW(publisher->publish(std::move(loaned_msg)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user