Skip to content

Race condition in MultiThreadedExecutor #1393

@felixdivo

Description

@felixdivo

Bug report

Required Info:

  • Operating System:
    • Ubuntu 24.04.1
  • Installation type:
    • Binaries
  • Version or commit hash:
    • rolling via ros-tooling/action-ros-ci@v0.3 GitHub action
  • DDS implementation:
    • unknown
  • Client library (if applicable):
    • rclpy

Steps to reproduce the issue

Documented here: felixdivo/ros2-easy-test#45. It is not 100% reproducible, yet it occurs frequently in my repo.

Expected behavior

No exception is raised.

Actual behavior

self = <rclpy.executors.MultiThreadedExecutor object at 0x7fb80c2f32f0>
timeout_sec = <rclpy.executors.TimeoutObject object at 0x7fb80c41cb90>
wait_condition = <bound method Future.done of <rclpy.task.Task object at 0x7fb80c2f30b0>>

    def _spin_once_impl(
        self,
        timeout_sec: Optional[Union[float, TimeoutObject]] = None,
        wait_condition: Callable[[], bool] = lambda: False
    ) -> None:
        try:
            handler, entity, node = self.wait_for_ready_callbacks(
                timeout_sec, None, wait_condition)
        except ExternalShutdownException:
            pass
        except ShutdownException:
            pass
        except TimeoutException:
            pass
        except ConditionReachedException:
            pass
        else:
            self._executor.submit(handler)
            self._futures.append(handler)
            # make a copy of the list that we iterate over while modifying it
            # (https://stackoverflow.com/q/1207406/3753684)
            for future in self._futures[:]:
                if future.done():
>                   self._futures.remove(future)
E                   ValueError: list.remove(x): x not in list

/opt/ros/rolling/lib/python3.12/site-packages/rclpy/executors.py:895: ValueError

Additional information

Previously, I had similar issues caused by race conditions reported in #1129. This again appears to be caused by multiple threads simultaneously acting on the executor. Is there some unintended usage by me?

Possible solution

If not, I suggest going with the it is "Easier to ask for forgiveness than permission" and just wrap the self._futures.remove(future) in a try-except block catching and ValueErrors. I can also do that if you confirm it is a reasonable path forward.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions