Skip to content

Commit bcdd663

Browse files
Feature: add executor.create_future() (#1495)
* feature: add create_future and test Signed-off-by: Nadav Elkabets <[email protected]> * Use create_future in all executor tests Signed-off-by: Nadav Elkabets <[email protected]> --------- Signed-off-by: Nadav Elkabets <[email protected]>
1 parent 50c284a commit bcdd663

File tree

4 files changed

+33
-8
lines changed

4 files changed

+33
-8
lines changed

rclpy/rclpy/executors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ def create_task(self, callback: Callable[..., Any], *args: Any, **kwargs: Any
260260
# Task inherits from Future
261261
return task
262262

263+
def create_future(self) -> Future:
264+
"""Create a Future object attached to the Executor."""
265+
return Future(executor=self)
266+
263267
def shutdown(self, timeout_sec: Optional[float] = None) -> bool:
264268
"""
265269
Stop executing callbacks and wait for their completion.

rclpy/src/rclpy/events_executor/events_executor.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ EventsExecutor::EventsExecutor(py::object context)
5252
inspect_iscoroutine_(py::module_::import("inspect").attr("iscoroutine")),
5353
inspect_signature_(py::module_::import("inspect").attr("signature")),
5454
rclpy_task_(py::module_::import("rclpy.task").attr("Task")),
55+
rclpy_future_(py::module_::import("rclpy.task").attr("Future")),
5556
rclpy_timer_timer_info_(py::module_::import("rclpy.timer").attr("TimerInfo")),
5657
signal_callback_([this]() {events_queue_.Stop();}),
5758
rcl_callback_manager_(&events_queue_),
@@ -78,6 +79,12 @@ pybind11::object EventsExecutor::create_task(
7879
return task;
7980
}
8081

82+
pybind11::object EventsExecutor::create_future()
83+
{
84+
using py::literals::operator""_a;
85+
return rclpy_future_("executor"_a = py::cast(this));
86+
}
87+
8188
bool EventsExecutor::shutdown(std::optional<double> timeout)
8289
{
8390
// NOTE: The rclpy context can invoke this with a lock on the context held. Therefore we must
@@ -897,6 +904,7 @@ void define_events_executor(py::object module)
897904
.def(py::init<py::object>(), py::arg("context"))
898905
.def_property_readonly("context", &EventsExecutor::get_context)
899906
.def("create_task", &EventsExecutor::create_task, py::arg("callback"))
907+
.def("create_future", &EventsExecutor::create_future)
900908
.def("shutdown", &EventsExecutor::shutdown, py::arg("timeout_sec") = py::none())
901909
.def("add_node", &EventsExecutor::add_node, py::arg("node"))
902910
.def("remove_node", &EventsExecutor::remove_node, py::arg("node"))

rclpy/src/rclpy/events_executor/events_executor.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class EventsExecutor
6767
pybind11::object get_context() const {return rclpy_context_;}
6868
pybind11::object create_task(
6969
pybind11::object callback, pybind11::args args = {}, const pybind11::kwargs & kwargs = {});
70+
pybind11::object create_future();
7071
bool shutdown(std::optional<double> timeout_sec = {});
7172
bool add_node(pybind11::object node);
7273
void remove_node(pybind11::handle node);
@@ -168,6 +169,7 @@ class EventsExecutor
168169
const pybind11::object inspect_iscoroutine_;
169170
const pybind11::object inspect_signature_;
170171
const pybind11::object rclpy_task_;
172+
const pybind11::object rclpy_future_;
171173
const pybind11::object rclpy_timer_timer_info_;
172174

173175
EventsQueue events_queue_;

rclpy/test/test_executor.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ def timer_callback() -> None:
494494
timer = self.node.create_timer(0.003, timer_callback)
495495

496496
# Timeout
497-
future = Future[None]()
497+
future = executor.create_future()
498498
self.assertFalse(future.done())
499499
start = time.perf_counter()
500500
executor.spin_until_future_complete(future=future, timeout_sec=0.1)
@@ -521,7 +521,7 @@ def set_future_result(future: Future[str]) -> None:
521521
future.set_result('finished')
522522

523523
# Future complete timeout_sec > 0
524-
future = Future[str]()
524+
future = executor.create_future()
525525
self.assertFalse(future.done())
526526
t = threading.Thread(target=lambda: set_future_result(future))
527527
t.start()
@@ -530,7 +530,7 @@ def set_future_result(future: Future[str]) -> None:
530530
self.assertEqual(future.result(), 'finished')
531531

532532
# Future complete timeout_sec = None
533-
future = Future()
533+
future = executor.create_future()
534534
self.assertFalse(future.done())
535535
t = threading.Thread(target=lambda: set_future_result(future))
536536
t.start()
@@ -539,7 +539,7 @@ def set_future_result(future: Future[str]) -> None:
539539
self.assertEqual(future.result(), 'finished')
540540

541541
# Future complete timeout < 0
542-
future = Future()
542+
future = executor.create_future()
543543
self.assertFalse(future.done())
544544
t = threading.Thread(target=lambda: set_future_result(future))
545545
t.start()
@@ -561,7 +561,7 @@ def timer_callback() -> None:
561561
timer = self.node.create_timer(0.003, timer_callback)
562562

563563
# Do not wait timeout_sec = 0
564-
future = Future[None]()
564+
future = executor.create_future()
565565
self.assertFalse(future.done())
566566
executor.spin_until_future_complete(future=future, timeout_sec=0)
567567
self.assertFalse(future.done())
@@ -644,7 +644,7 @@ def test_single_threaded_spin_once_until_future(self) -> None:
644644
with self.subTest(cls=cls):
645645
executor = cls(context=self.context)
646646

647-
future = Future[bool](executor=executor)
647+
future = executor.create_future()
648648

649649
# Setup a thread to spin_once_until_future_complete, which will spin
650650
# for a maximum of 10 seconds.
@@ -672,7 +672,7 @@ def test_multi_threaded_spin_once_until_future(self) -> None:
672672
self.assertIsNotNone(self.node.handle)
673673
executor = MultiThreadedExecutor(context=self.context)
674674

675-
future: Future[bool] = Future(executor=executor)
675+
future: Future[bool] = executor.create_future()
676676

677677
# Setup a thread to spin_once_until_future_complete, which will spin
678678
# for a maximum of 10 seconds.
@@ -721,7 +721,7 @@ def timer2_callback() -> None:
721721
timer2 = self.node.create_timer(1.5, timer2_callback, callback_group)
722722

723723
executor.add_node(self.node)
724-
future = Future[None](executor=executor)
724+
future = executor.create_future()
725725
executor.spin_until_future_complete(future, 4)
726726

727727
assert count == 2
@@ -731,6 +731,17 @@ def timer2_callback() -> None:
731731
self.node.destroy_timer(timer1)
732732
self.node.destroy_client(cli)
733733

734+
def test_create_future_returns_future_with_executor_attached(self) -> None:
735+
self.assertIsNotNone(self.node.handle)
736+
for cls in [SingleThreadedExecutor, MultiThreadedExecutor, EventsExecutor]:
737+
with self.subTest(cls=cls):
738+
executor = cls(context=self.context)
739+
try:
740+
fut = executor.create_future()
741+
self.assertEqual(executor, fut._executor())
742+
finally:
743+
executor.shutdown()
744+
734745

735746
if __name__ == '__main__':
736747
unittest.main()

0 commit comments

Comments
 (0)