fix timers in task
Fixes #985.
I am not familiar with the executor logic in rclpy, so this is a hacky fix I did. Instead of passing a new timeout each tick, I added self._spin_until_future_complete_timer and self._spin_until_future_complete_timeout to wake the executor when the timeout is reached. Also added test_timer_in_task.py to test the new behavior.
Looking at the code of spin_until_future_complete, it behaves differently depending on if a timeout is provided.
if timeout_sec is None or timeout_sec < 0:
while self._context.ok() and not future.done() and not self._is_shutdown:
self.spin_once_until_future_complete(future, timeout_sec)
else:
start = time.monotonic()
end = start + timeout_sec
timeout_left = timeout_sec
while self._context.ok() and not future.done() and not self._is_shutdown:
self.spin_once_until_future_complete(future, timeout_left)
now = time.monotonic()
if now >= end:
return
timeout_left = end - now
This all looks good but furthur investigation of wait_for_ready_callbacks (which is eventually called later down the chain),
while True:
if self._cb_iter is None or self._last_args != args or self._last_kwargs != kwargs:
# Create a new generator
self._last_args = args
self._last_kwargs = kwargs
self._cb_iter = self._wait_for_ready_callbacks(*args, **kwargs)
try:
return next(self._cb_iter)
except StopIteration:
# Generator ran out of work
self._cb_iter = None
It creates a new generator whenever _last_args or _last_kwargs is different, the "timeout" path of spin_until_future_complete does exactly that, each spin_once_until_future_complete is passed a different timeout.
My guess is that somehow creating new generators every "tick" causes new "work" to be created, and executing a "work" causes a new generator to be created, resulting in a infinite list of pending "work", which causes the actual task to never be executed.