fix(executor): wake executor when future is set by non-executor thread (#1916)#3126
Open
aki1770-del wants to merge 1 commit intoros2:rollingfrom
Open
Conversation
spin_until_future_complete with no timeout passed -1ns to spin_once_impl, which called wait_for_work(-1ns) → wait_set_.wait(-1ns). If the future was fulfilled by a thread outside the executor, no wait-set entity became ready and the executor blocked forever. See ros2#1916. Fix: spawn a watcher thread inside spin_until_future_complete that polls future.wait_for(100ms) and calls interrupt_guard_condition_->trigger() the moment the future is ready, immediately unblocking wait_for_work. A stop_watcher flag allows clean join() on timeout or cancel exit paths. Add regression test testSpinUntilFutureCompleteExternalThread: no executor entities registered, future set by external thread after 50ms, expects SUCCESS without blocking. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
spin_until_future_completewith no timeout (-1) blocks indefinitely when thefuture is fulfilled by a thread that is not part of the executor.
Root cause (
executor.hpp/executor.cpp):spin_until_future_complete_impl(-1ns)passes the full remaining timeout tospin_once_impl, which callswait_for_work(-1ns)→wait_set_.wait(-1ns)wait_set_.waitblocks until a registered entity (subscription, timer, service,client, or guard condition) becomes ready
wait_for_worknever returnsThis was originally reported alongside a potential
GuardConditioncallback bug(#1917), but that path was subsequently fixed. The remaining issue is that the
executor has no mechanism to learn the future is ready when it is set externally.
Fix
Spawn a watcher thread inside
spin_until_future_completethat monitors thefuture and triggers
interrupt_guard_condition_the moment the future is set.This immediately unblocks
wait_for_workwithout busy-waiting in the executor'smain loop.
The watcher uses
future.wait_for(100ms)in a loop (rather thanfuture.wait())so it can observe a
stop_watcherflag set on all exit paths — enabling a cleanjoin()whether the spin returns by success, timeout, cancel, or exception.Files changed:
rclcpp/include/rclcpp/executor.hpp— expandspin_until_future_completetemplate; add
#include <thread>rclcpp/test/rclcpp/executors/test_executors.cpp— addtestSpinUntilFutureCompleteExternalThreadregression testPrior work consulted
The watcher-thread approach is consistent with the
interrupt_guard_condition_pattern already used in
Executor::cancel()(executor.cppL472) andExecutor::handle_updated_entities()(executor.cppL146).Source reading
rclcpp/include/rclcpp/executor.hppL571 —interrupt_guard_condition_declarationrclcpp/src/rclcpp/executor.cppL265–314 —spin_until_future_complete_implrclcpp/src/rclcpp/executor.cppL913–930 —get_next_executable→wait_for_workrclcpp/src/rclcpp/executor.cppL758–787 —wait_for_work→wait_set_.waitrclcpp/src/rclcpp/guard_condition.cppL69–85 —trigger()already callsrcl_trigger_guard_conditionbefore callback (sloretz's 2022 bug was separately fixed)AI-assisted — authored with Claude, reviewed by Komada.