Skip to content

Commit 168c5cc

Browse files
author
Brad Martin
committed
Fix stale Future done callbacks with spin_until_future_complete()
This method can't be allowed to leave its Future done callback outstanding in case the method is returning for a reason other than the Future being done. Signed-off-by: Brad Martin <[email protected]>
1 parent df3dbb7 commit 168c5cc

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

rclpy/rclpy/task.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ def add_done_callback(self, callback: Callable[['Future[T]'], None]) -> None:
195195
if invoke:
196196
callback(self)
197197

198+
def remove_done_callback(self, callback: Callable[['Future[T]'], None]) -> bool:
199+
"""
200+
Remove a previously-added done callback.
201+
202+
Returns true if the given callback was found and removed. Always fails if the Future was
203+
already complete.
204+
"""
205+
with self._lock:
206+
try:
207+
self._callbacks.remove(callback)
208+
except ValueError:
209+
return False
210+
return True
211+
198212

199213
class Task(Future[T]):
200214
"""

rclpy/src/rclpy/events_executor/events_executor.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,15 @@ void EventsExecutor::spin(std::optional<double> timeout_sec, bool stop_after_use
227227
void EventsExecutor::spin_until_future_complete(
228228
py::handle future, std::optional<double> timeout_sec, bool stop_after_user_callback)
229229
{
230-
future.attr("add_done_callback")(py::cpp_function([this](py::handle) {io_context_.stop();}));
230+
py::cpp_function cb([this](py::handle) {io_context_.stop();});
231+
future.attr("add_done_callback")(cb);
231232
spin(timeout_sec, stop_after_user_callback);
233+
// In case the future didn't complete (we hit the timeout or dispatched a different user callback
234+
// after being asked to only run one), we need to clean up our callback; otherwise, it could fire
235+
// later when the executor isn't valid, or we haven't been asked to wait for this future; also,
236+
// we could end up adding a bunch more of these same callbacks if this method gets invoked in a
237+
// loop.
238+
future.attr("remove_done_callback")(cb);
232239
}
233240

234241
EventsExecutor * EventsExecutor::enter() {return this;}

0 commit comments

Comments
 (0)