Skip to content

Commit 22223f1

Browse files
christophebedard-apexaihliberackifujitatomoya
committed
Add support for spin_until_timeout (take 2)
Co-authored-by: Hubert Liberacki <hliberacki@gmail.com> Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com> Signed-off-by: Hubert Liberacki <hliberacki@gmail.com> Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> Signed-off-by: Christophe Bedard <christophe.bedard@apex.ai>
1 parent f9c4894 commit 22223f1

29 files changed

+468
-322
lines changed

rclcpp/include/rclcpp/client.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,11 @@ class Client : public ClientBase
608608
/// Send a request to the service server.
609609
/**
610610
* This method returns a `FutureAndRequestId` instance
611-
* that can be passed to Executor::spin_until_future_complete() to
611+
* that can be passed to Executor::spin_until_complete() to
612612
* wait until it has been completed.
613613
*
614614
* If the future never completes,
615-
* e.g. the call to Executor::spin_until_future_complete() times out,
615+
* e.g. the call to Executor::spin_until_complete() times out,
616616
* Client::remove_pending_request() must be called to clean the client internal state.
617617
* Not doing so will make the `Client` instance to use more memory each time a response is not
618618
* received from the service server.
@@ -621,7 +621,7 @@ class Client : public ClientBase
621621
* auto future = client->async_send_request(my_request);
622622
* if (
623623
* rclcpp::FutureReturnCode::TIMEOUT ==
624-
* executor->spin_until_future_complete(future, timeout))
624+
* executor->spin_until_complete(future, timeout))
625625
* {
626626
* client->remove_pending_request(future);
627627
* // handle timeout

rclcpp/include/rclcpp/executor.hpp

+97-46
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <memory>
2626
#include <mutex>
2727
#include <string>
28+
#include <type_traits>
2829
#include <vector>
2930

3031
#include "rcl/guard_condition.h"
@@ -350,6 +351,51 @@ class Executor
350351
virtual void
351352
spin_once(std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));
352353

354+
/// Spin (blocking) until the condition is complete, it times out waiting,
355+
/// or rclcpp is interrupted.
356+
/**
357+
* \param[in] future The condition which can be callable or future type to wait on.
358+
* If this function returns SUCCESS, the future can be
359+
* accessed without blocking (though it may still throw an exception).
360+
* \param[in] timeout Optional timeout parameter, which gets passed to Executor::spin_node_once.
361+
* `-1` is block forever, `0` is non-blocking.
362+
* If the time spent inside the blocking loop exceeds this timeout, return a TIMEOUT return
363+
* code.
364+
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
365+
*/
366+
template<typename ConditionT, typename DurationT = std::chrono::milliseconds>
367+
FutureReturnCode
368+
spin_until_complete(
369+
const ConditionT & condition,
370+
DurationT timeout = DurationT(-1))
371+
{
372+
if constexpr (std::is_invocable_v<ConditionT>) {
373+
using RetT = std::invoke_result_t<ConditionT>;
374+
static_assert(
375+
std::is_same_v<bool, RetT>,
376+
"Conditional callable has to return boolean type");
377+
return spin_until_complete_impl(condition, timeout);
378+
} else {
379+
auto check_future = [&condition]() {
380+
return condition.wait_for(std::chrono::seconds(0)) ==
381+
std::future_status::ready;
382+
};
383+
return spin_until_complete_impl(check_future, timeout);
384+
}
385+
}
386+
387+
/// Spin (blocking) for at least the given amount of duration.
388+
/**
389+
* \param[in] duration gets passed to Executor::spin_node_once,
390+
* spins the executor for given duration.
391+
*/
392+
template<typename DurationT>
393+
void
394+
spin_for(DurationT duration)
395+
{
396+
(void)spin_until_complete([]() {return false;}, duration);
397+
}
398+
353399
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
354400
/**
355401
* \param[in] future The future to wait on. If this function returns SUCCESS, the future can be
@@ -361,57 +407,13 @@ class Executor
361407
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
362408
*/
363409
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
410+
[[deprecated("use spin_until_complete(const ConditionT & condition, DurationT timeout) instead")]]
364411
FutureReturnCode
365412
spin_until_future_complete(
366413
const FutureT & future,
367414
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
368415
{
369-
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
370-
// inside a callback executed by an executor.
371-
372-
// Check the future before entering the while loop.
373-
// If the future is already complete, don't try to spin.
374-
std::future_status status = future.wait_for(std::chrono::seconds(0));
375-
if (status == std::future_status::ready) {
376-
return FutureReturnCode::SUCCESS;
377-
}
378-
379-
auto end_time = std::chrono::steady_clock::now();
380-
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
381-
timeout);
382-
if (timeout_ns > std::chrono::nanoseconds::zero()) {
383-
end_time += timeout_ns;
384-
}
385-
std::chrono::nanoseconds timeout_left = timeout_ns;
386-
387-
if (spinning.exchange(true)) {
388-
throw std::runtime_error("spin_until_future_complete() called while already spinning");
389-
}
390-
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
391-
while (rclcpp::ok(this->context_) && spinning.load()) {
392-
// Do one item of work.
393-
spin_once_impl(timeout_left);
394-
395-
// Check if the future is set, return SUCCESS if it is.
396-
status = future.wait_for(std::chrono::seconds(0));
397-
if (status == std::future_status::ready) {
398-
return FutureReturnCode::SUCCESS;
399-
}
400-
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
401-
if (timeout_ns < std::chrono::nanoseconds::zero()) {
402-
continue;
403-
}
404-
// Otherwise check if we still have time to wait, return TIMEOUT if not.
405-
auto now = std::chrono::steady_clock::now();
406-
if (now >= end_time) {
407-
return FutureReturnCode::TIMEOUT;
408-
}
409-
// Subtract the elapsed time from the original timeout.
410-
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
411-
}
412-
413-
// The future did not complete before ok() returned false, return INTERRUPTED.
414-
return FutureReturnCode::INTERRUPTED;
416+
return spin_until_complete(future, timeout);
415417
}
416418

417419
/// Cancel any running spin* function, causing it to return.
@@ -570,6 +572,55 @@ class Executor
570572
virtual void
571573
spin_once_impl(std::chrono::nanoseconds timeout);
572574

575+
protected:
576+
// Implementation details, used by spin_until_complete and spin_for.
577+
// Previous implementation of spin_until_future_complete.
578+
template<typename ConditionT, typename DurationT>
579+
FutureReturnCode
580+
spin_until_complete_impl(ConditionT condition, DurationT timeout)
581+
{
582+
auto end_time = std::chrono::steady_clock::now();
583+
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
584+
timeout);
585+
if (timeout_ns > std::chrono::nanoseconds::zero()) {
586+
end_time += timeout_ns;
587+
}
588+
std::chrono::nanoseconds timeout_left = timeout_ns;
589+
590+
// Preliminary check, finish if conditon is done already.
591+
if (condition()) {
592+
return FutureReturnCode::SUCCESS;
593+
}
594+
595+
if (spinning.exchange(true)) {
596+
throw std::runtime_error("spin_until_complete() called while already spinning");
597+
}
598+
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
599+
while (rclcpp::ok(this->context_) && spinning.load()) {
600+
// Do one item of work.
601+
spin_once_impl(timeout_left);
602+
603+
if (condition()) {
604+
return FutureReturnCode::SUCCESS;
605+
}
606+
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
607+
if (timeout_ns < std::chrono::nanoseconds::zero()) {
608+
continue;
609+
}
610+
// Otherwise check if we still have time to wait, return TIMEOUT if not.
611+
auto now = std::chrono::steady_clock::now();
612+
if (now >= end_time) {
613+
return FutureReturnCode::TIMEOUT;
614+
}
615+
// Subtract the elapsed time from the original timeout.
616+
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
617+
}
618+
619+
// The condition did not pass before ok() returned false, return INTERRUPTED.
620+
return FutureReturnCode::INTERRUPTED;
621+
}
622+
623+
public:
573624
/// Waitable containing guard conditions controlling the executor flow.
574625
/**
575626
* This waitable contains the interrupt and shutdown guard condition, as well

rclcpp/include/rclcpp/executors.hpp

+86-9
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,50 @@ namespace executors
6767
using rclcpp::executors::MultiThreadedExecutor;
6868
using rclcpp::executors::SingleThreadedExecutor;
6969

70+
/// Spin (blocking) until the conditon is complete, it times out waiting, or rclcpp is interrupted.
71+
/**
72+
* \param[in] executor The executor which will spin the node.
73+
* \param[in] node_ptr The node to spin.
74+
* \param[in] condition The callable or future to wait on. If `SUCCESS`, the condition is safe to
75+
* access after this function
76+
* \param[in] timeout Optional timeout parameter, which gets passed to
77+
* Executor::spin_node_once.
78+
* `-1` is block forever, `0` is non-blocking.
79+
* If the time spent inside the blocking loop exceeds this timeout, return a `TIMEOUT` return code.
80+
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
81+
*/
82+
template<typename ConditionT, typename DurationT = std::chrono::milliseconds>
83+
rclcpp::FutureReturnCode
84+
spin_node_until_complete(
85+
rclcpp::Executor & executor,
86+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
87+
const ConditionT & condition,
88+
DurationT timeout = DurationT(-1))
89+
{
90+
// TODO(wjwwood): does not work recursively; can't call spin_node_until_complete
91+
// inside a callback executed by an executor.
92+
executor.add_node(node_ptr);
93+
auto retcode = executor.spin_until_complete(condition, timeout);
94+
executor.remove_node(node_ptr);
95+
return retcode;
96+
}
97+
98+
template<typename NodeT = rclcpp::Node, typename ConditionT,
99+
typename DurationT = std::chrono::milliseconds>
100+
rclcpp::FutureReturnCode
101+
spin_node_until_complete(
102+
rclcpp::Executor & executor,
103+
std::shared_ptr<NodeT> node_ptr,
104+
const ConditionT & condition,
105+
DurationT timeout = DurationT(-1))
106+
{
107+
return rclcpp::executors::spin_node_until_complete(
108+
executor,
109+
node_ptr->get_node_base_interface(),
110+
condition,
111+
timeout);
112+
}
113+
70114
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
71115
/**
72116
* \param[in] executor The executor which will spin the node.
@@ -80,31 +124,34 @@ using rclcpp::executors::SingleThreadedExecutor;
80124
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
81125
*/
82126
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
127+
[[deprecated(
128+
"use spin_node_until_complete(Executor &, node_interfaces::NodeBaseInterface::SharedPtr, "
129+
"const ConditionT &, DurationT) instead"
130+
)]]
83131
rclcpp::FutureReturnCode
84132
spin_node_until_future_complete(
85133
rclcpp::Executor & executor,
86134
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
87135
const FutureT & future,
88136
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
89137
{
90-
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
91-
// inside a callback executed by an executor.
92-
executor.add_node(node_ptr);
93-
auto retcode = executor.spin_until_future_complete(future, timeout);
94-
executor.remove_node(node_ptr);
95-
return retcode;
138+
return spin_until_complete(executor, node_ptr, future, timeout);
96139
}
97140

98141
template<typename NodeT = rclcpp::Node, typename FutureT, typename TimeRepT = int64_t,
99142
typename TimeT = std::milli>
143+
[[deprecated(
144+
"use spin_node_until_complete(Executor &, std::shared_ptr<NodeT>, "
145+
"const ConditionT &, DurationT) instead"
146+
)]]
100147
rclcpp::FutureReturnCode
101148
spin_node_until_future_complete(
102149
rclcpp::Executor & executor,
103150
std::shared_ptr<NodeT> node_ptr,
104151
const FutureT & future,
105152
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
106153
{
107-
return rclcpp::executors::spin_node_until_future_complete(
154+
return rclcpp::executors::spin_node_until_complete(
108155
executor,
109156
node_ptr->get_node_base_interface(),
110157
future,
@@ -113,26 +160,56 @@ spin_node_until_future_complete(
113160

114161
} // namespace executors
115162

163+
template<typename ConditionT, typename DurationT = std::chrono::milliseconds>
164+
rclcpp::FutureReturnCode
165+
spin_until_complete(
166+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
167+
const ConditionT & condition,
168+
DurationT timeout = DurationT(-1))
169+
{
170+
rclcpp::executors::SingleThreadedExecutor executor;
171+
return executors::spin_node_until_complete<ConditionT>(executor, node_ptr, condition, timeout);
172+
}
173+
174+
template<typename NodeT = rclcpp::Node, typename ConditionT,
175+
typename DurationT = std::chrono::milliseconds>
176+
rclcpp::FutureReturnCode
177+
spin_until_complete(
178+
std::shared_ptr<NodeT> node_ptr,
179+
const ConditionT & condition,
180+
DurationT timeout = DurationT(-1))
181+
{
182+
return rclcpp::spin_until_complete(node_ptr->get_node_base_interface(), condition, timeout);
183+
}
184+
116185
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
186+
[[deprecated(
187+
"use spin_until_complete(node_interfaces::NodeBaseInterface::SharedPtr, "
188+
"const ConditionT &,DurationT) instead"
189+
)]]
117190
rclcpp::FutureReturnCode
118191
spin_until_future_complete(
119192
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
120193
const FutureT & future,
121194
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
122195
{
123196
rclcpp::executors::SingleThreadedExecutor executor;
124-
return executors::spin_node_until_future_complete<FutureT>(executor, node_ptr, future, timeout);
197+
return executors::spin_node_until_complete<FutureT>(executor, node_ptr, future, timeout);
125198
}
126199

127200
template<typename NodeT = rclcpp::Node, typename FutureT, typename TimeRepT = int64_t,
128201
typename TimeT = std::milli>
202+
[[deprecated(
203+
"use spin_until_complete(std::shared_ptr<NodeT>, const ConditionT &, "
204+
"DurationT) instead"
205+
)]]
129206
rclcpp::FutureReturnCode
130207
spin_until_future_complete(
131208
std::shared_ptr<NodeT> node_ptr,
132209
const FutureT & future,
133210
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
134211
{
135-
return rclcpp::spin_until_future_complete(node_ptr->get_node_base_interface(), future, timeout);
212+
return rclcpp::spin_until_complete(node_ptr->get_node_base_interface(), future, timeout);
136213
}
137214

138215
} // namespace rclcpp

rclcpp/include/rclcpp/future_return_code.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
namespace rclcpp
2424
{
2525

26-
/// Return codes to be used with spin_until_future_complete.
26+
/// Return codes to be used with spin_until_complete.
2727
/**
2828
* SUCCESS: The future is complete and can be accessed with "get" without blocking.
2929
* This does not indicate that the operation succeeded; "get" may still throw an exception.

rclcpp/include/rclcpp/generic_client.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ class GenericClient : public ClientBase
100100
/// Send a request to the service server.
101101
/**
102102
* This method returns a `FutureAndRequestId` instance
103-
* that can be passed to Executor::spin_until_future_complete() to
103+
* that can be passed to Executor::spin_until_complete() to
104104
* wait until it has been completed.
105105
*
106106
* If the future never completes,
107-
* e.g. the call to Executor::spin_until_future_complete() times out,
107+
* e.g. the call to Executor::spin_until_complete() times out,
108108
* GenericClient::remove_pending_request() must be called to clean the client internal state.
109109
* Not doing so will make the `Client` instance to use more memory each time a response is not
110110
* received from the service server.
@@ -113,7 +113,7 @@ class GenericClient : public ClientBase
113113
* auto future = client->async_send_request(my_request);
114114
* if (
115115
* rclcpp::FutureReturnCode::TIMEOUT ==
116-
* executor->spin_until_future_complete(future, timeout))
116+
* executor->spin_until_complete(future, timeout))
117117
* {
118118
* client->remove_pending_request(future);
119119
* // handle timeout

rclcpp/include/rclcpp/rclcpp.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
* - Executors (responsible for execution of callbacks through a blocking spin):
7070
* - rclcpp::spin()
7171
* - rclcpp::spin_some()
72-
* - rclcpp::spin_until_future_complete()
72+
* - rclcpp::spin_until_complete()
7373
* - rclcpp::executors::SingleThreadedExecutor
7474
* - rclcpp::executors::SingleThreadedExecutor::add_node()
7575
* - rclcpp::executors::SingleThreadedExecutor::spin()

0 commit comments

Comments
 (0)