Compare commits

...

11 Commits

Author SHA1 Message Date
Marco A. Gutierrez
2cd8900dd2 28.2.0 2024-04-26 16:57:57 +00:00
Marco A. Gutierrez
cf6141330a Changelog.
Signed-off-by: Marco A. Gutierrez <marcogg@marcogg.com>
2024-04-26 16:57:52 +00:00
Sharmin Ramli
de666d2bf4 Check for negative time in rclcpp::Time(int64_t nanoseconds, ...) constructor (#2510)
Signed-off-by: Nursharmin Ramli <nursharminramli@gmail.com>
2024-04-23 22:47:56 +02:00
Barry Xu
55939613a0 Revise the description of service configure_introspection() (#2511)
Signed-off-by: Barry Xu <barry.xu@sony.com>
2024-04-23 13:29:46 +02:00
Chris Lalancette
3d58f0fb70 28.1.0 2024-04-16 17:16:42 +00:00
Chris Lalancette
3fd0831af2 Changelog.
Signed-off-by: Chris Lalancette <clalancette@gmail.com>
2024-04-16 17:16:35 +00:00
Chris Lalancette
d648a7c926 Remove references to index.ros.org. (#2504)
Signed-off-by: Chris Lalancette <clalancette@gmail.com>
2024-04-16 08:42:38 -07:00
William Woodall
6bb9407b90 Reduce overhead for inheriting from rclcpp::Executor when base functionality is not reused (#2506)
* feat(clock): Added function to interrupt sleep

This is useful in case a second thread needs to wake up another
thread, that is sleeping using a clock.

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>

* feat: Added support for multi thread wait / shutdown

This adds support for multiple threads waiting on the same
clock, while an shutdown is invoked.

Signed-off-by: Janosch Machowinski <j.machowinski@cellumation.com>

* chore: Made Executor fully overloadable

This commit makes every public funciton virtual, and adds virtual impl
function for the existing template functions.

The goal of this commit is to be able to fully control the everything from
a derived class.

Signed-off-by: Janosch Machowinski <j.machowinski@cellumation.com>

* Update rclcpp/include/rclcpp/executor.hpp

Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>

* Update rclcpp/include/rclcpp/executor.hpp

Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>

* Update rclcpp/include/rclcpp/executor.hpp

Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>

* Update rclcpp/include/rclcpp/executor.hpp

Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>

* docs: added doc string for spin_until_future_complete_impl

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>

* made is_spinning not virtual

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>

* feat: Changed interface of spin_until_future_complete_impl

This change allows it to use a second thread to wait for
the future to become ready.

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>

* doc fixup

Signed-off-by: William Woodall <william@osrfoundation.org>

* undo template->implicit conversion change

Signed-off-by: William Woodall <william@osrfoundation.org>

* style

Signed-off-by: William Woodall <william@osrfoundation.org>

---------

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
Signed-off-by: Janosch Machowinski <j.machowinski@cellumation.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>
Signed-off-by: William Woodall <william@osrfoundation.org>
Co-authored-by: Janosch Machowinski <J.Machowinski@cellumation.com>
Co-authored-by: jmachowinski <jmachowinski@users.noreply.github.com>
2024-04-16 08:35:56 -07:00
Alejandro Hernández Cordero
535d56f690 28.0.1 2024-04-16 15:43:00 +02:00
Alejandro Hernández Cordero
1351737f34 Changelog
Signed-off-by: Alejandro Hernández Cordero <ahcorde@gmail.com>
2024-04-16 15:42:55 +02:00
jmachowinski
839348c601 Callback after cancel (#2281)
* feat(Client): Added function to stop callbacks of a goal handle

This function allows us to drop the handle in a locked context.
If we do not do this within a lock, there will be a race condition between
the deletion of the shared_ptr of the handle and the result / feedback
callbacks.

* fix: make Client goal handle recursive

This fixes deadlocks due to release of goal handles in callbacks etc.

* fix(ActionGoalClient): Fixed memory leak for nominal case

This fixes a memory leak due to a self reference in the ClientGoalHandle.
Note, this fix will only work, if the ClientGoalHandle ever receives
a result callback.

* doc: Updated documentation of rclcpp_action::Client::async_send_goal
* docs: Made the async_send_goal documentation more explicit

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
Co-authored-by: Janosch Machowinski <J.Machowinski@cellumation.com>
2024-04-15 12:05:17 -05:00
25 changed files with 707 additions and 172 deletions

View File

@@ -8,7 +8,7 @@ rclcpp provides the standard C++ API for interacting with ROS 2.
`#include "rclcpp/rclcpp.hpp"` allows use of the most common elements of the ROS 2 system.
The link to the latest API documentation can be found on the rclcpp package info page, at the [ROS Index](https://index.ros.org/p/rclcpp/).
The link to the latest API documentation can be found on the [rclcpp package info page](https://docs.ros.org/en/rolling/p/rclcpp).
### Examples

View File

@@ -2,6 +2,153 @@
Changelog for package rclcpp
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28.2.0 (2024-04-26)
-------------------
* Check for negative time in rclcpp::Time(int64_t nanoseconds, ...) constructor (`#2510 <https://github.com/ros2/rclcpp/issues/2510>`_)
* Revise the description of service configure_introspection() (`#2511 <https://github.com/ros2/rclcpp/issues/2511>`_)
* Contributors: Barry Xu, Sharmin Ramli
28.1.0 (2024-04-16)
-------------------
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
* Reduce overhead for inheriting from rclcpp::Executor when base functionality is not reused (`#2506 <https://github.com/ros2/rclcpp/issues/2506>`_)
* Contributors: Chris Lalancette, William Woodall, jmachowinski
28.0.1 (2024-04-16)
-------------------
* [wjwwood] Updated "Data race fixes" (`#2500 <https://github.com/ros2/rclcpp/issues/2500>`_)
* Fix callback group logic in executor
* fix: Fixed unnecessary copy of wait_set
* fix(executor): Fixed race conditions with rebuild of wait_sets
Before this change, the rebuild of wait set would be triggered
after the wait set was waken up. With bad timing, this could
lead to the rebuild not happening with multi threaded executor.
* fix(Executor): Fixed lost of entities rebuild request
* chore: Added assert for not set callback_group in execute_any_executable
* Add test for cbg getting reset
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
* chore: renamed test cases to snake_case
* style
* fixup test to avoid polling and short timeouts
* fix: Use correct notify_waitable\_ instance
* fix(StaticSingleThreadedExecutor): Added missing special case handling for current_notify_waitable\_
* fix(TestCallbackGroup): Fixed test after change to timers
---------
Co-authored-by: Janosch Machowinski <j.machowinski@cellumation.com>
Co-authored-by: Michael Carroll <mjcarroll@intrinsic.ai>
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
* fixup var names to snake case (`#2501 <https://github.com/ros2/rclcpp/issues/2501>`_)
* Added optional TimerInfo to timer callback (`#2343 <https://github.com/ros2/rclcpp/issues/2343>`_)
Co-authored-by: Alexis Tsogias <a.tsogias@cellumation.com>
Co-authored-by: Janosch Machowinski <J.Machowinski@cellumation.com>
* Fix uninitialized memory in test (`#2498 <https://github.com/ros2/rclcpp/issues/2498>`_)
When I added in the tests for large messages, I made a mistake and reserved space in the strings, but didn't actually expand it. Thus, we were writing into uninitialized memory. Fix this by just using the correct constructor for string, which will allocate and initialize the memory properly.
* Ensure waitables handle guard condition retriggering (`#2483 <https://github.com/ros2/rclcpp/issues/2483>`_)
Co-authored-by: Michael Carroll <mjcarroll@intrinsic.ai>
* fix: init concatenated_vector with begin() & end() (`#2492 <https://github.com/ros2/rclcpp/issues/2492>`_)
* this commit will fix the warning [-Wstringop-overflow=] `#2461 <https://github.com/ros2/rclcpp/issues/2461>`_
* Use the same context for the specified node in rclcpp::spin functions (`#2433 <https://github.com/ros2/rclcpp/issues/2433>`_)
* Use the same conext for the specified node in rclcpp::spin_xx functions
* Add test for spinning with non-default-context
* Format code
---------
* Disable compare-function-pointers in test_utilities (`#2489 <https://github.com/ros2/rclcpp/issues/2489>`_)
* address ambiguous auto variable. (`#2481 <https://github.com/ros2/rclcpp/issues/2481>`_)
* Increase the cppcheck timeout to 1200 seconds (`#2484 <https://github.com/ros2/rclcpp/issues/2484>`_)
* Removed test_timers_manager clang warning (`#2479 <https://github.com/ros2/rclcpp/issues/2479>`_)
* Flaky timer test fix (`#2469 <https://github.com/ros2/rclcpp/issues/2469>`_)
* fix(time_source): Fixed possible race condition
* fix(test_executors_time_cancel_behaviour): Fixed multiple race conditions
---------
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
* Add tracepoint for generic publisher/subscriber (`#2448 <https://github.com/ros2/rclcpp/issues/2448>`_)
* update rclcpp::Waitable API to use references and const (`#2467 <https://github.com/ros2/rclcpp/issues/2467>`_)
* Utilize rclcpp::WaitSet as part of the executors (`#2142 <https://github.com/ros2/rclcpp/issues/2142>`_)
* Deprecate callback_group call taking context
* Add base executor objects that can be used by implementors
* Template common operations
* Address reviewer feedback:
* Add callback to EntitiesCollector constructor
* Make function to check automatically added callback groups take a list
* Lint
* Address reviewer feedback and fix templates
* Lint and docs
* Make executor own the notify waitable
* Add pending queue to collector, remove from waitable
Also change node's get_guard_condition to return shared_ptr
* Change interrupt guard condition to shared_ptr
Check if guard condition is valid before adding it to the waitable
* Lint and docs
* Utilize rclcpp::WaitSet as part of the executors
* Don't exchange atomic twice
* Fix add_node and add more tests
* Make get_notify_guard_condition follow API tick-tock
* Improve callback group tick-tocking
* Don't lock twice
* Address reviewer feedback
* Add thread safety annotations and make locks consistent
* @wip
* Reset callback groups for multithreaded executor
* Avoid many small function calls when building executables
* Re-trigger guard condition if buffer has data
* Address reviewer feedback
* Trace points
* Remove tracepoints
* Reducing diff
* Reduce diff
* Uncrustify
* Restore tests
* Back to weak_ptr and reduce test time
* reduce diff and lint
* Restore static single threaded tests that weren't working before
* Restore more tests
* Fix multithreaded test
* Fix assert
* Fix constructor test
* Change ready_executables signature back
* Don't enforce removing callback groups before nodes
* Remove the "add_valid_node" API
* Only notify if the trigger condition is valid
* Only trigger if valid and needed
* Fix spin_some/spin_all implementation
* Restore single threaded executor
* Picking ABI-incompatible executor changes
* Add PIMPL
* Additional waitset prune
* Fix bad merge
* Expand test timeout
* Introduce method to clear expired entities from a collection
* Make sure to call remove_expired_entities().
* Prune queued work when callback group is removed
* Prune subscriptions from dynamic storage
* Styles fixes.
* Re-trigger guard conditions
* Condense to just use watiable.take_data
* Lint
* Address reviewer comments (nits)
* Lock mutex when copying
* Refactors to static single threaded based on reviewers
* More small refactoring
* Lint
* Lint
* Add ready executable accessors to WaitResult
* Make use of accessors from wait_set
* Fix tests
* Fix more tests
* Tidy up single threaded executor implementation
* Don't null out timer, rely on call
* change how timers are checked from wait result in executors
* peak -> peek
* fix bug in next_waitable logic
* fix bug in StaticSTE that broke the add callback groups to executor tests
* style
---------
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
Co-authored-by: William Woodall <william@osrfoundation.org>
* fix flakiness in TestTimersManager unit-test (`#2468 <https://github.com/ros2/rclcpp/issues/2468>`_)
the previous version of the test was relying on the assumption that a timer with 1ms period gets called at least 6 times if the main thread waits 15ms. this is true most of the times, but it's not guaranteed, especially when running the test on windows CI servers. the new version of the test makes no assumptions on how much time it takes for the timers manager to invoke the timers, but rather focuses on ensuring that they are called the right amount of times, which is what's important for the purpose of the test
* Contributors: Alberto Soragna, Alejandro Hernández Cordero, Chris Lalancette, Homalozoa X, Kotaro Yoshimoto, Michael Carroll, Tomoya Fujita, William Woodall, h-suzuki-isp, jmachowinski
28.0.0 (2024-03-28)
-------------------
* fix spin_some_max_duration unit-test for events-executor (`#2465 <https://github.com/ros2/rclcpp/issues/2465>`_)

View File

@@ -2,7 +2,7 @@
The ROS client library in C++.
The link to the latest rclcpp API documentation, which includes a complete list of its main components and features, can be found on the rclcpp package info page, at the [ROS Index](https://index.ros.org/p/rclcpp/).
The link to the latest rclcpp API documentation, which includes a complete list of its main components and features, can be found on the [rclcpp package info page](https://docs.ros.org/en/rolling/p/rclcpp).
## Quality Declaration

View File

@@ -193,6 +193,16 @@ public:
bool
ros_time_is_active();
/**
* Cancels an ongoing or future sleep operation of one thread.
*
* This function can be used by one thread, to wakeup another thread that is
* blocked using any of the sleep_ or wait_ methods of this class.
*/
RCLCPP_PUBLIC
void
cancel_sleep_or_wait();
/// Return the rcl_clock_t clock handle
RCLCPP_PUBLIC
rcl_clock_t *

View File

@@ -272,12 +272,12 @@ public:
* \param[in] node Shared pointer to the node to add.
*/
RCLCPP_PUBLIC
void
virtual void
spin_node_some(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node);
/// Convenience function which takes Node and forwards NodeBaseInterface.
RCLCPP_PUBLIC
void
virtual void
spin_node_some(std::shared_ptr<rclcpp::Node> node);
/// Collect work once and execute all available work, optionally within a max duration.
@@ -307,14 +307,14 @@ public:
* \param[in] node Shared pointer to the node to add.
*/
RCLCPP_PUBLIC
void
virtual void
spin_node_all(
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node,
std::chrono::nanoseconds max_duration);
/// Convenience function which takes Node and forwards NodeBaseInterface.
RCLCPP_PUBLIC
void
virtual void
spin_node_all(std::shared_ptr<rclcpp::Node> node, std::chrono::nanoseconds max_duration);
/// Collect and execute work repeatedly within a duration or until no more work is available.
@@ -366,52 +366,12 @@ public:
const FutureT & future,
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
{
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
// inside a callback executed by an executor.
// Check the future before entering the while loop.
// If the future is already complete, don't try to spin.
std::future_status status = future.wait_for(std::chrono::seconds(0));
if (status == std::future_status::ready) {
return FutureReturnCode::SUCCESS;
}
auto end_time = std::chrono::steady_clock::now();
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
timeout);
if (timeout_ns > std::chrono::nanoseconds::zero()) {
end_time += timeout_ns;
}
std::chrono::nanoseconds timeout_left = timeout_ns;
if (spinning.exchange(true)) {
throw std::runtime_error("spin_until_future_complete() called while already spinning");
}
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
while (rclcpp::ok(this->context_) && spinning.load()) {
// Do one item of work.
spin_once_impl(timeout_left);
// Check if the future is set, return SUCCESS if it is.
status = future.wait_for(std::chrono::seconds(0));
if (status == std::future_status::ready) {
return FutureReturnCode::SUCCESS;
return spin_until_future_complete_impl(
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout),
[&future](std::chrono::nanoseconds wait_time) {
return future.wait_for(wait_time);
}
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
if (timeout_ns < std::chrono::nanoseconds::zero()) {
continue;
}
// Otherwise check if we still have time to wait, return TIMEOUT if not.
auto now = std::chrono::steady_clock::now();
if (now >= end_time) {
return FutureReturnCode::TIMEOUT;
}
// Subtract the elapsed time from the original timeout.
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
}
// The future did not complete before ok() returned false, return INTERRUPTED.
return FutureReturnCode::INTERRUPTED;
);
}
/// Cancel any running spin* function, causing it to return.
@@ -420,7 +380,7 @@ public:
* \throws std::runtime_error if there is an issue triggering the guard condition
*/
RCLCPP_PUBLIC
void
virtual void
cancel();
/// Returns true if the executor is currently spinning.
@@ -433,6 +393,14 @@ public:
is_spinning();
protected:
/// Constructor that will not initialize any non-trivial members.
/**
* This constructor is intended to be used by any derived executor
* that explicitly does not want to use the default implementation provided
* by this class.
*/
explicit Executor(const std::shared_ptr<rclcpp::Context> & context);
/// Add a node to executor, execute the next available unit of work, and remove the node.
/**
* Implementation of spin_node_once using std::chrono::nanoseconds
@@ -447,6 +415,23 @@ protected:
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node,
std::chrono::nanoseconds timeout);
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
/**
* \sa spin_until_future_complete()
* The only difference with spin_until_future_complete() is that the future's
* type is obscured through a std::function which lets you wait on it
* reguardless of type.
*
* \param[in] timeout see spin_until_future_complete() for details
* \param[in] wait_for_future function to wait on the future and get the
* status after waiting
*/
RCLCPP_PUBLIC
virtual FutureReturnCode
spin_until_future_complete_impl(
std::chrono::nanoseconds timeout,
const std::function<std::future_status(std::chrono::nanoseconds wait_time)> & wait_for_future);
/// Collect work and execute available work, optionally within a duration.
/**
* Implementation of spin_some and spin_all.

View File

@@ -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

View File

@@ -49,6 +49,7 @@ public:
/**
* \param nanoseconds since time epoch
* \param clock_type clock type
* \throws std::runtime_error if nanoseconds are negative
*/
RCLCPP_PUBLIC
explicit Time(int64_t nanoseconds = 0, rcl_clock_type_t clock_type = RCL_SYSTEM_TIME);

View File

@@ -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.0.0</version>
<version>28.2.0</version>
<description>The ROS client library in C++.</description>
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>

View File

@@ -49,6 +49,10 @@ public:
rcl_clock_t rcl_clock_;
rcl_allocator_t allocator_;
bool stop_sleeping_ = false;
bool shutdown_ = false;
std::condition_variable cv_;
std::mutex wait_mutex_;
std::mutex clock_mutex_;
};
@@ -79,8 +83,20 @@ Clock::now() const
return now;
}
void
Clock::cancel_sleep_or_wait()
{
{
std::unique_lock lock(impl_->wait_mutex_);
impl_->stop_sleeping_ = true;
}
impl_->cv_.notify_one();
}
bool
Clock::sleep_until(Time until, Context::SharedPtr context)
Clock::sleep_until(
Time until,
Context::SharedPtr context)
{
if (!context || !context->is_valid()) {
throw std::runtime_error("context cannot be slept with because it's invalid");
@@ -91,12 +107,14 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
}
bool time_source_changed = false;
std::condition_variable cv;
// Wake this thread if the context is shutdown
rclcpp::OnShutdownCallbackHandle shutdown_cb_handle = context->add_on_shutdown_callback(
[&cv]() {
cv.notify_one();
[this]() {
{
std::unique_lock lock(impl_->wait_mutex_);
impl_->shutdown_ = true;
}
impl_->cv_.notify_one();
});
// No longer need the shutdown callback when this function exits
auto callback_remover = rcpputils::scope_exit(
@@ -112,22 +130,24 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
const std::chrono::steady_clock::time_point chrono_until =
chrono_entry + std::chrono::nanoseconds(delta_t.nanoseconds());
// loop over spurious wakeups but notice shutdown
std::unique_lock lock(impl_->clock_mutex_);
while (now() < until && context->is_valid()) {
cv.wait_until(lock, chrono_until);
// loop over spurious wakeups but notice shutdown or stop of sleep
std::unique_lock lock(impl_->wait_mutex_);
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid()) {
impl_->cv_.wait_until(lock, chrono_until);
}
impl_->stop_sleeping_ = false;
} else if (this_clock_type == RCL_SYSTEM_TIME) {
auto system_time = std::chrono::system_clock::time_point(
// Cast because system clock resolution is too big for nanoseconds on some systems
std::chrono::duration_cast<std::chrono::system_clock::duration>(
std::chrono::nanoseconds(until.nanoseconds())));
// loop over spurious wakeups but notice shutdown
std::unique_lock lock(impl_->clock_mutex_);
while (now() < until && context->is_valid()) {
cv.wait_until(lock, system_time);
// loop over spurious wakeups but notice shutdown or stop of sleep
std::unique_lock lock(impl_->wait_mutex_);
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid()) {
impl_->cv_.wait_until(lock, system_time);
}
impl_->stop_sleeping_ = false;
} else if (this_clock_type == RCL_ROS_TIME) {
// Install jump handler for any amount of time change, for two purposes:
// - if ROS time is active, check if time reached on each new clock sample
@@ -139,11 +159,12 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
threshold.min_forward.nanoseconds = 1;
auto clock_handler = create_jump_callback(
nullptr,
[&cv, &time_source_changed](const rcl_time_jump_t & jump) {
[this, &time_source_changed](const rcl_time_jump_t & jump) {
if (jump.clock_change != RCL_ROS_TIME_NO_CHANGE) {
std::lock_guard<std::mutex> lk(impl_->wait_mutex_);
time_source_changed = true;
}
cv.notify_one();
impl_->cv_.notify_one();
},
threshold);
@@ -153,19 +174,25 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
std::chrono::duration_cast<std::chrono::system_clock::duration>(
std::chrono::nanoseconds(until.nanoseconds())));
// loop over spurious wakeups but notice shutdown or time source change
std::unique_lock lock(impl_->clock_mutex_);
while (now() < until && context->is_valid() && !time_source_changed) {
cv.wait_until(lock, system_time);
// loop over spurious wakeups but notice shutdown, stop of sleep or time source change
std::unique_lock lock(impl_->wait_mutex_);
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid() &&
!time_source_changed)
{
impl_->cv_.wait_until(lock, system_time);
}
impl_->stop_sleeping_ = false;
} else {
// RCL_ROS_TIME with ros_time_is_active.
// Just wait without "until" because installed
// jump callbacks wake the cv on every new sample.
std::unique_lock lock(impl_->clock_mutex_);
while (now() < until && context->is_valid() && !time_source_changed) {
cv.wait(lock);
std::unique_lock lock(impl_->wait_mutex_);
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid() &&
!time_source_changed)
{
impl_->cv_.wait(lock);
}
impl_->stop_sleeping_ = false;
}
}

View File

@@ -50,6 +50,14 @@ static constexpr rclcpp::SubscriptionWaitSetMask kDefaultSubscriptionMask = {tru
class rclcpp::ExecutorImplementation {};
Executor::Executor(const std::shared_ptr<rclcpp::Context> & context)
: spinning(false),
entities_need_rebuild_(true),
collector_(nullptr),
wait_set_({}, {}, {}, {}, {}, {}, context)
{
}
Executor::Executor(const rclcpp::ExecutorOptions & options)
: spinning(false),
interrupt_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context)),
@@ -120,7 +128,8 @@ Executor::~Executor()
}
}
void Executor::trigger_entity_recollect(bool notify)
void
Executor::trigger_entity_recollect(bool notify)
{
this->entities_need_rebuild_.store(true);
@@ -240,6 +249,59 @@ Executor::spin_node_once_nanoseconds(
this->remove_node(node, false);
}
rclcpp::FutureReturnCode
Executor::spin_until_future_complete_impl(
std::chrono::nanoseconds timeout,
const std::function<std::future_status(std::chrono::nanoseconds wait_time)> & wait_for_future)
{
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
// inside a callback executed by an executor.
// Check the future before entering the while loop.
// If the future is already complete, don't try to spin.
std::future_status status = wait_for_future(std::chrono::seconds(0));
if (status == std::future_status::ready) {
return FutureReturnCode::SUCCESS;
}
auto end_time = std::chrono::steady_clock::now();
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
timeout);
if (timeout_ns > std::chrono::nanoseconds::zero()) {
end_time += timeout_ns;
}
std::chrono::nanoseconds timeout_left = timeout_ns;
if (spinning.exchange(true)) {
throw std::runtime_error("spin_until_future_complete() called while already spinning");
}
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
while (rclcpp::ok(this->context_) && spinning.load()) {
// Do one item of work.
spin_once_impl(timeout_left);
// Check if the future is set, return SUCCESS if it is.
status = wait_for_future(std::chrono::seconds(0));
if (status == std::future_status::ready) {
return FutureReturnCode::SUCCESS;
}
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
if (timeout_ns < std::chrono::nanoseconds::zero()) {
continue;
}
// Otherwise check if we still have time to wait, return TIMEOUT if not.
auto now = std::chrono::steady_clock::now();
if (now >= end_time) {
return FutureReturnCode::TIMEOUT;
}
// Subtract the elapsed time from the original timeout.
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
}
// The future did not complete before ok() returned false, return INTERRUPTED.
return FutureReturnCode::INTERRUPTED;
}
void
Executor::spin_node_some(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node)
{

View File

@@ -60,6 +60,10 @@ Time::Time(int32_t seconds, uint32_t nanoseconds, rcl_clock_type_t clock_type)
Time::Time(int64_t nanoseconds, rcl_clock_type_t clock_type)
: rcl_time_(init_time_point(clock_type))
{
if (nanoseconds < 0) {
throw std::runtime_error("cannot store a negative time point in rclcpp::Time");
}
rcl_time_.nanoseconds = nanoseconds;
}
@@ -249,6 +253,9 @@ Time::operator+=(const rclcpp::Duration & rhs)
}
rcl_time_.nanoseconds += rhs.nanoseconds();
if (rcl_time_.nanoseconds < 0) {
throw std::runtime_error("cannot store a negative time point in rclcpp::Time");
}
return *this;
}
@@ -264,6 +271,9 @@ Time::operator-=(const rclcpp::Duration & rhs)
}
rcl_time_.nanoseconds -= rhs.nanoseconds();
if (rcl_time_.nanoseconds < 0) {
throw std::runtime_error("cannot store a negative time point in rclcpp::Time");
}
return *this;
}

View File

@@ -55,6 +55,10 @@ ament_add_gtest(test_client test_client.cpp)
if(TARGET test_client)
target_link_libraries(test_client ${PROJECT_NAME} mimick ${rcl_interfaces_TARGETS} ${test_msgs_TARGETS})
endif()
ament_add_gtest(test_clock test_clock.cpp)
if(TARGET test_clock)
target_link_libraries(test_clock ${PROJECT_NAME} mimick ${rcl_interfaces_TARGETS} ${test_msgs_TARGETS})
endif()
ament_add_gtest(test_copy_all_parameter_values test_copy_all_parameter_values.cpp)
if(TARGET test_copy_all_parameter_values)
target_link_libraries(test_copy_all_parameter_values ${PROJECT_NAME})

View File

@@ -0,0 +1,229 @@
// Copyright 2024 Cellumation GmbH
//
// 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 "rcl/error_handling.h"
#include "rcl/time.h"
#include "rclcpp/clock.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/time_source.hpp"
#include "../utils/rclcpp_gtest_macros.hpp"
using namespace std::chrono_literals;
class TestClockWakeup : public ::testing::TestWithParam<rcl_clock_type_e>
{
public:
void test_wakeup_before_sleep(const rclcpp::Clock::SharedPtr & clock)
{
std::atomic_bool thread_finished = false;
std::thread wait_thread = std::thread(
[&clock, &thread_finished]()
{
// make sure the thread starts sleeping late
std::this_thread::sleep_for(std::chrono::milliseconds(100));
clock->sleep_until(clock->now() + std::chrono::seconds(3));
thread_finished = true;
});
// notify the clock, that the sleep shall be interrupted
clock->cancel_sleep_or_wait();
auto start_time = std::chrono::steady_clock::now();
auto cur_time = start_time;
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
cur_time = std::chrono::steady_clock::now();
}
wait_thread.join();
EXPECT_TRUE(thread_finished);
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
}
void test_wakeup_after_sleep(const rclcpp::Clock::SharedPtr & clock)
{
std::atomic_bool thread_finished = false;
std::thread wait_thread = std::thread(
[&clock, &thread_finished]()
{
clock->sleep_until(clock->now() + std::chrono::seconds(3));
thread_finished = true;
});
// make sure the thread is already sleeping before we send the cancel
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// notify the clock, that the sleep shall be interrupted
clock->cancel_sleep_or_wait();
auto start_time = std::chrono::steady_clock::now();
auto cur_time = start_time;
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
cur_time = std::chrono::steady_clock::now();
}
wait_thread.join();
EXPECT_TRUE(thread_finished);
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
}
protected:
static void SetUpTestCase()
{
rclcpp::init(0, nullptr);
}
static void TearDownTestCase()
{
rclcpp::shutdown();
}
void SetUp()
{
node = std::make_shared<rclcpp::Node>("my_node");
}
void TearDown()
{
node.reset();
}
rclcpp::Node::SharedPtr node;
};
INSTANTIATE_TEST_SUITE_P(
Clocks,
TestClockWakeup,
::testing::Values(
RCL_SYSTEM_TIME, RCL_ROS_TIME, RCL_STEADY_TIME
));
TEST_P(TestClockWakeup, wakeup_sleep) {
auto clock = std::make_shared<rclcpp::Clock>(GetParam());
test_wakeup_after_sleep(clock);
test_wakeup_before_sleep(clock);
}
TEST_F(TestClockWakeup, wakeup_sleep_ros_time_active) {
node->set_parameter({"use_sim_time", true});
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
rclcpp::TimeSource time_source(node);
time_source.attachClock(clock);
EXPECT_TRUE(clock->ros_time_is_active());
test_wakeup_after_sleep(clock);
test_wakeup_before_sleep(clock);
}
TEST_F(TestClockWakeup, no_wakeup_on_sim_time) {
node->set_parameter({"use_sim_time", true});
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
rclcpp::TimeSource time_source(node);
time_source.attachClock(clock);
EXPECT_TRUE(clock->ros_time_is_active());
std::atomic_bool thread_finished = false;
std::thread wait_thread = std::thread(
[&clock, &thread_finished]()
{
// make sure the thread starts sleeping late
clock->sleep_until(clock->now() + std::chrono::milliseconds(10));
thread_finished = true;
});
// make sure, that the sim time clock does not wakeup, as no clock is provided
std::this_thread::sleep_for(std::chrono::milliseconds(500));
EXPECT_FALSE(thread_finished);
// notify the clock, that the sleep shall be interrupted
clock->cancel_sleep_or_wait();
auto start_time = std::chrono::steady_clock::now();
auto cur_time = start_time;
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
cur_time = std::chrono::steady_clock::now();
}
wait_thread.join();
EXPECT_TRUE(thread_finished);
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
}
TEST_F(TestClockWakeup, multiple_threads_wait_on_one_clock) {
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
std::vector<bool> thread_finished(10, false);
std::vector<std::thread> threads;
for (size_t nr = 0; nr < thread_finished.size(); nr++) {
threads.push_back(
std::thread(
[&clock, &thread_finished, nr]()
{
// make sure the thread starts sleeping late
clock->sleep_until(clock->now() + std::chrono::seconds(10));
thread_finished[nr] = true;
}));
}
// wait a bit so all threads can execute the sleep_until
std::this_thread::sleep_for(std::chrono::milliseconds(500));
for (const bool & finished : thread_finished) {
EXPECT_FALSE(finished);
}
rclcpp::shutdown();
auto start_time = std::chrono::steady_clock::now();
auto cur_time = start_time;
bool threads_finished = false;
while (!threads_finished && start_time + std::chrono::seconds(1) > cur_time) {
threads_finished = true;
for (const bool finished : thread_finished) {
if (!finished) {
threads_finished = false;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
cur_time = std::chrono::steady_clock::now();
}
for (const bool finished : thread_finished) {
EXPECT_TRUE(finished);
}
for (auto & thread : threads) {
thread.join();
}
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
}

View File

@@ -138,6 +138,8 @@ TEST_F(TestTime, conversions) {
EXPECT_ANY_THROW(rclcpp::Time(-1, 1));
EXPECT_ANY_THROW(rclcpp::Time(-1));
EXPECT_ANY_THROW(
{
rclcpp::Time assignment(1, 2);
@@ -168,48 +170,6 @@ TEST_F(TestTime, conversions) {
EXPECT_EQ(time_msg.nanosec, HALF_SEC_IN_NS);
EXPECT_EQ(rclcpp::Time(time_msg).nanoseconds(), ONE_AND_HALF_SEC_IN_NS);
}
{
// Can rclcpp::Time be negative or not? The following constructor works:
rclcpp::Time time(-HALF_SEC_IN_NS);
auto time_msg = static_cast<builtin_interfaces::msg::Time>(time);
EXPECT_EQ(time_msg.sec, -1);
EXPECT_EQ(time_msg.nanosec, HALF_SEC_IN_NS);
// The opposite conversion throws...
EXPECT_ANY_THROW(
{
rclcpp::Time negative_time(time_msg);
});
}
{
// Can rclcpp::Time be negative or not? The following constructor works:
rclcpp::Time time(-ONE_SEC_IN_NS);
auto time_msg = static_cast<builtin_interfaces::msg::Time>(time);
EXPECT_EQ(time_msg.sec, -1);
EXPECT_EQ(time_msg.nanosec, 0u);
// The opposite conversion throws...
EXPECT_ANY_THROW(
{
rclcpp::Time negative_time(time_msg);
});
}
{
// Can rclcpp::Time be negative or not? The following constructor works:
rclcpp::Time time(-ONE_AND_HALF_SEC_IN_NS);
auto time_msg = static_cast<builtin_interfaces::msg::Time>(time);
EXPECT_EQ(time_msg.sec, -2);
EXPECT_EQ(time_msg.nanosec, HALF_SEC_IN_NS);
// The opposite conversion throws...
EXPECT_ANY_THROW(
{
rclcpp::Time negative_time(time_msg);
});
}
}
TEST_F(TestTime, operators) {
@@ -326,31 +286,18 @@ TEST_F(TestTime, overflow_detectors) {
TEST_F(TestTime, overflows) {
rclcpp::Time max_time(std::numeric_limits<rcl_time_point_value_t>::max());
rclcpp::Time min_time(std::numeric_limits<rcl_time_point_value_t>::min());
rclcpp::Duration one(1ns);
rclcpp::Duration two(2ns);
// Cross min/max
// Cross max
EXPECT_THROW(max_time + one, std::overflow_error);
EXPECT_THROW(min_time - one, std::underflow_error);
EXPECT_THROW(max_time - min_time, std::overflow_error);
EXPECT_THROW(min_time - max_time, std::underflow_error);
EXPECT_THROW(rclcpp::Time(max_time) += one, std::overflow_error);
EXPECT_THROW(rclcpp::Time(min_time) -= one, std::underflow_error);
EXPECT_NO_THROW(max_time - max_time);
EXPECT_NO_THROW(min_time - min_time);
// Cross zero in both directions
// Cross zero
rclcpp::Time one_time(1);
EXPECT_NO_THROW(one_time - two);
EXPECT_NO_THROW(rclcpp::Time(one_time) -= two);
rclcpp::Time minus_one_time(-1);
EXPECT_NO_THROW(minus_one_time + two);
EXPECT_NO_THROW(rclcpp::Time(minus_one_time) += two);
EXPECT_NO_THROW(one_time - minus_one_time);
EXPECT_NO_THROW(minus_one_time - one_time);
EXPECT_THROW(one_time - two, std::runtime_error);
EXPECT_THROW(rclcpp::Time(one_time) -= two, std::runtime_error);
rclcpp::Time two_time(2);
EXPECT_NO_THROW(one_time - two_time);
@@ -432,41 +379,24 @@ TEST_F(TestTime, test_overflow_underflow_throws) {
RCLCPP_EXPECT_THROW_EQ(
test_time = rclcpp::Time(INT64_MAX) + rclcpp::Duration(1ns),
std::overflow_error("addition leads to int64_t overflow"));
RCLCPP_EXPECT_THROW_EQ(
test_time = rclcpp::Time(INT64_MIN) + rclcpp::Duration(-1ns),
std::underflow_error("addition leads to int64_t underflow"));
RCLCPP_EXPECT_THROW_EQ(
test_time = rclcpp::Time(INT64_MAX) - rclcpp::Duration(-1ns),
std::overflow_error("time subtraction leads to int64_t overflow"));
RCLCPP_EXPECT_THROW_EQ(
test_time = rclcpp::Time(INT64_MIN) - rclcpp::Duration(1ns),
std::underflow_error("time subtraction leads to int64_t underflow"));
test_time = rclcpp::Time(INT64_MAX);
RCLCPP_EXPECT_THROW_EQ(
test_time += rclcpp::Duration(1ns),
std::overflow_error("addition leads to int64_t overflow"));
test_time = rclcpp::Time(INT64_MIN);
RCLCPP_EXPECT_THROW_EQ(
test_time += rclcpp::Duration(-1ns),
std::underflow_error("addition leads to int64_t underflow"));
test_time = rclcpp::Time(INT64_MAX);
RCLCPP_EXPECT_THROW_EQ(
test_time -= rclcpp::Duration(-1ns),
std::overflow_error("time subtraction leads to int64_t overflow"));
test_time = rclcpp::Time(INT64_MIN);
RCLCPP_EXPECT_THROW_EQ(
test_time -= rclcpp::Duration(1ns),
std::underflow_error("time subtraction leads to int64_t underflow"));
RCLCPP_EXPECT_THROW_EQ(
test_time = rclcpp::Duration::from_nanoseconds(INT64_MAX) + rclcpp::Time(1),
std::overflow_error("addition leads to int64_t overflow"));
RCLCPP_EXPECT_THROW_EQ(
test_time = rclcpp::Duration::from_nanoseconds(INT64_MIN) + rclcpp::Time(-1),
std::underflow_error("addition leads to int64_t underflow"));
}
class TestClockSleep : public ::testing::Test

View File

@@ -3,6 +3,41 @@ Changelog for package rclcpp_action
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28.2.0 (2024-04-26)
-------------------
28.1.0 (2024-04-16)
-------------------
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
* Contributors: Chris Lalancette
28.0.1 (2024-04-16)
-------------------
* Callback after cancel (`#2281 <https://github.com/ros2/rclcpp/issues/2281>`_)
* feat(Client): Added function to stop callbacks of a goal handle
This function allows us to drop the handle in a locked context.
If we do not do this within a lock, there will be a race condition between
the deletion of the shared_ptr of the handle and the result / feedback
callbacks.
* fix: make Client goal handle recursive
This fixes deadlocks due to release of goal handles in callbacks etc.
* fix(ActionGoalClient): Fixed memory leak for nominal case
This fixes a memory leak due to a self reference in the ClientGoalHandle.
Note, this fix will only work, if the ClientGoalHandle ever receives
a result callback.
* doc: Updated documentation of rclcpp_action::Client::async_send_goal
* docs: Made the async_send_goal documentation more explicit
Co-authored-by: Janosch Machowinski <J.Machowinski@cellumation.com>
* Remake of "fix: Fixed race condition in action server between is_ready and take" (`#2495 <https://github.com/ros2/rclcpp/issues/2495>`_)
Some background information: is_ready, take_data and execute data
may be called from different threads in any order. The code in the old
state expected them to be called in series, without interruption.
This lead to multiple race conditions, as the state of the pimpl objects
was altered by the three functions in a non thread safe way.
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
* update rclcpp::Waitable API to use references and const (`#2467 <https://github.com/ros2/rclcpp/issues/2467>`_)
* Contributors: William Woodall, jmachowinski
28.0.0 (2024-03-28)
-------------------
* Do not generate the exception when action service response timeout. (`#2464 <https://github.com/ros2/rclcpp/issues/2464>`_)

View File

@@ -2,7 +2,7 @@
Adds action APIs for C++.
The link to the latest rclcpp_action API documentation, which includes a complete list of its main components and features, can be found on the rclcpp_action package info page, at the [ROS Index](https://index.ros.org/p/rclcpp_action/).
The link to the latest rclcpp_action API documentation, which includes a complete list of its main components and features, can be found on the [rclcpp_action package info page](https://docs.ros.org/en/rolling/p/rclcpp_action).
For more information about Actions in ROS 2, see the [design document](http://design.ros2.org/articles/actions.html).
## Quality Declaration

View File

@@ -405,12 +405,22 @@ public:
/// Send an action goal and asynchronously get the result.
/**
* If the goal is accepted by an action server, the returned future is set to a `ClientGoalHandle`.
* If the goal is accepted by an action server, the returned future is set to a `GoalHandle::SharedPtr`.
* If the goal is rejected by an action server, then the future is set to a `nullptr`.
*
* The returned goal handle is used to monitor the status of the goal and get the final result.
* It is valid as long as you hold a reference to the shared pointer or until the
* rclcpp_action::Client is destroyed at which point the goal status will become UNKNOWN.
* The goal handle in the future is used to monitor the status of the goal and get the final result.
*
* If callbacks were set in @param options, you will receive callbacks, as long as you hold a reference
* to the shared pointer contained in the returned future, or rclcpp_action::Client is destroyed. Dropping
* the shared pointer to the goal handle will not cancel the goal. In order to cancel it, you must explicitly
* call async_cancel_goal.
*
* WARNING this method has inconsistent behaviour and a memory leak bug.
* If you set the result callback in @param options, the handle will be self referencing, and you will receive
* callbacks even though you do not hold a reference to the shared pointer. In this case, the self reference will
* be deleted if the result callback was received. If there is no result callback, there will be a memory leak.
*
* To prevent the memory leak, you may call stop_callbacks() explicit. This will delete the self reference.
*
* \param[in] goal The goal request.
* \param[in] options Options for sending the goal request. Contains references to callbacks for
@@ -448,7 +458,7 @@ public:
std::shared_ptr<GoalHandle> goal_handle(
new GoalHandle(goal_info, options.feedback_callback, options.result_callback));
{
std::lock_guard<std::mutex> guard(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
goal_handles_[goal_handle->get_goal_id()] = goal_handle;
}
promise->set_value(goal_handle);
@@ -466,7 +476,7 @@ public:
// To prevent the list from growing out of control, forget about any goals
// with no more user references
{
std::lock_guard<std::mutex> guard(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
auto goal_handle_it = goal_handles_.begin();
while (goal_handle_it != goal_handles_.end()) {
if (!goal_handle_it->second.lock()) {
@@ -496,7 +506,7 @@ public:
typename GoalHandle::SharedPtr goal_handle,
ResultCallback result_callback = nullptr)
{
std::lock_guard<std::mutex> lock(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> lock(goal_handles_mutex_);
if (goal_handles_.count(goal_handle->get_goal_id()) == 0) {
throw exceptions::UnknownGoalHandleError();
}
@@ -531,7 +541,7 @@ public:
typename GoalHandle::SharedPtr goal_handle,
CancelCallback cancel_callback = nullptr)
{
std::lock_guard<std::mutex> lock(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> lock(goal_handles_mutex_);
if (goal_handles_.count(goal_handle->get_goal_id()) == 0) {
throw exceptions::UnknownGoalHandleError();
}
@@ -562,6 +572,63 @@ public:
return async_cancel(cancel_request, cancel_callback);
}
/// Stops the callbacks for the goal in a thread safe way
/**
* This will NOT cancel the goal, it will only stop the callbacks.
*
* After the call to this function, it is guaranteed that there
* will be no more callbacks from the goal. This is not guaranteed
* if multiple threads are involved, and the goal_handle is just
* dropped.
*
* \param[in] goal_handle The goal were the callbacks shall be stopped
*/
void stop_callbacks(typename GoalHandle::SharedPtr goal_handle)
{
goal_handle->set_feedback_callback(typename GoalHandle::FeedbackCallback());
goal_handle->set_result_callback(typename GoalHandle::ResultCallback());
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
const GoalUUID & goal_id = goal_handle->get_goal_id();
auto it = goal_handles_.find(goal_id);
if (goal_handles_.end() == it) {
// someone else already deleted the entry
// e.g. the result callback
RCLCPP_DEBUG(
this->get_logger(),
"Given goal is unknown. Ignoring...");
return;
}
goal_handles_.erase(it);
}
/// Stops the callbacks for the goal in a thread safe way
/**
* For futher information see stop_callbacks(typename GoalHandle::SharedPtr goal_handle)
*/
void stop_callbacks(const GoalUUID & goal_id)
{
typename GoalHandle::SharedPtr goal_handle;
{
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
auto it = goal_handles_.find(goal_id);
if (goal_handles_.end() == it) {
// someone else already deleted the entry
// e.g. the result callback
RCLCPP_DEBUG(
this->get_logger(),
"Given goal is unknown. Ignoring...");
return;
}
goal_handle = it->lock();
}
if (goal_handle) {
stop_callbacks(goal_handle);
}
}
/// Asynchronously request all goals at or before a specified time be canceled.
/**
* \param[in] stamp The timestamp for the cancel goal request.
@@ -590,7 +657,7 @@ public:
virtual
~Client()
{
std::lock_guard<std::mutex> guard(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
auto it = goal_handles_.begin();
while (it != goal_handles_.end()) {
typename GoalHandle::SharedPtr goal_handle = it->second.lock();
@@ -637,7 +704,7 @@ private:
void
handle_feedback_message(std::shared_ptr<void> message) override
{
std::lock_guard<std::mutex> guard(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
using FeedbackMessage = typename ActionT::Impl::FeedbackMessage;
typename FeedbackMessage::SharedPtr feedback_message =
std::static_pointer_cast<FeedbackMessage>(message);
@@ -674,7 +741,7 @@ private:
void
handle_status_message(std::shared_ptr<void> message) override
{
std::lock_guard<std::mutex> guard(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> guard(goal_handles_mutex_);
using GoalStatusMessage = typename ActionT::Impl::GoalStatusMessage;
auto status_message = std::static_pointer_cast<GoalStatusMessage>(message);
for (const GoalStatus & status : status_message->status_list) {
@@ -723,7 +790,7 @@ private:
wrapped_result.goal_id = goal_handle->get_goal_id();
wrapped_result.code = static_cast<ResultCode>(result_response->status);
goal_handle->set_result(wrapped_result);
std::lock_guard<std::mutex> lock(goal_handles_mutex_);
std::lock_guard<std::recursive_mutex> lock(goal_handles_mutex_);
goal_handles_.erase(goal_handle->get_goal_id());
});
} catch (rclcpp::exceptions::RCLError & ex) {
@@ -755,7 +822,7 @@ private:
}
std::map<GoalUUID, typename GoalHandle::WeakPtr> goal_handles_;
std::mutex goal_handles_mutex_;
std::recursive_mutex goal_handles_mutex_;
};
} // namespace rclcpp_action

View File

@@ -75,6 +75,7 @@ ClientGoalHandle<ActionT>::set_result(const WrappedResult & wrapped_result)
result_promise_.set_value(wrapped_result);
if (result_callback_) {
result_callback_(wrapped_result);
result_callback_ = ResultCallback();
}
}

View File

@@ -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.0.0</version>
<version>28.2.0</version>
<description>Adds action APIs for C++.</description>
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>

View File

@@ -2,6 +2,17 @@
Changelog for package rclcpp_components
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28.2.0 (2024-04-26)
-------------------
28.1.0 (2024-04-16)
-------------------
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
* Contributors: Chris Lalancette
28.0.1 (2024-04-16)
-------------------
28.0.0 (2024-03-28)
-------------------
* Add EXECUTOR docs (`#2440 <https://github.com/ros2/rclcpp/issues/2440>`_)

View File

@@ -2,7 +2,7 @@
Package containing tools for dynamically loadable components.
The link to the latest rclcpp_components API documentation, which includes a complete list of its main components and features, can be found on the rclcpp_components package info page, at the [ROS Index](https://index.ros.org/p/rclcpp_components/).
The link to the latest rclcpp_components API documentation, which includes a complete list of its main components and features, can be found on the [rclcpp_components package info page](https://docs.ros.org/en/rolling/p/rclcpp_components).
## Quality Declaration

View File

@@ -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.0.0</version>
<version>28.2.0</version>
<description>Package containing tools for dynamically loadable components</description>
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>

View File

@@ -3,6 +3,22 @@ Changelog for package rclcpp_lifecycle
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28.2.0 (2024-04-26)
-------------------
28.1.0 (2024-04-16)
-------------------
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
* Contributors: Chris Lalancette
28.0.1 (2024-04-16)
-------------------
* call shutdown in LifecycleNode dtor to avoid leaving the device in un… (`#2450 <https://github.com/ros2/rclcpp/issues/2450>`_)
* call shutdown in LifecycleNode dtor to avoid leaving the device in unknown state.
* add test to verify LifecycleNode::shutdown is called on destructor.
---------
* Contributors: Tomoya Fujita
28.0.0 (2024-03-28)
-------------------
* Update quality declaration documents (`#2427 <https://github.com/ros2/rclcpp/issues/2427>`_)

View File

@@ -2,7 +2,7 @@
Package containing a prototype for lifecycle implementation.
The link to the latest rclcpp_lifecycle API documentation, which includes a complete list of its main components and features, can be found on the rclcpp_lifecycle package info page, at the [ROS Index](https://index.ros.org/p/rclcpp_lifecycle/).
The link to the latest rclcpp_lifecycle API documentation, which includes a complete list of its main components and features, can be found on the [rclcpp_lifecycle package info page](https://docs.ros.org/en/rolling/p/rclcpp_lifecycle).
For more information about LifeCycle in ROS 2, see the [design document](http://design.ros2.org/articles/node_lifecycle.html).
## Quality Declaration

View File

@@ -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.0.0</version>
<version>28.2.0</version>
<description>Package containing a prototype for lifecycle implementation</description>
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>