🤖
Introduction to ROS2 and Robotics
  • Introduction
  • ROS2
    • Index
    • IDE and CMake Setup
      • How to add additional include search path
    • ROS2 Building Blocks
      • ROS Workspace and Package Layout
      • Launch File
      • tf2
      • Quality of Service
      • Configurations
        • Rviz Configuration
      • Built-in Types
        • Built-in Message Type
    • ROS Architecture
      • Intra-process Communication
    • Navigation and Planning
      • Navigation Stack and Concepts
      • Navigation2 Implementation Overview
        • 🏗️Cost Map
        • Obstacle Avoidance and DWB Controller
      • DWB Controller
      • Page 5
    • How to launch the Nav2 stack
    • ROS2 Control
      • Online Resources
      • Overview of Codebase
    • 🍳Cookbook
      • Useful Commands
      • How to specify parameters
      • How to build the workspace
      • 🏗️How to publish message to a topic from command line?
      • How to inspect service and make a service call
      • How to properly terminate ROS and Gazebo
      • How to add and remove models in Gazebo simulation dynamically
      • 🚧How to spin nodes
    • 🛒Tutorials
      • Services and Communication between ROS2 and Gazebo
      • Subscription and Message Filters Demo
      • Executor and Spin Explained
      • Lifecycle Node Demo
      • Robotic Arm Demo
      • ⚒️Multiple Robotic Arms Simulation Demo
      • 🚧Introduction to xacro
    • Page
    • 🍺Tech Blog
      • Difference between URDF and SDF and how to convert
  • Gazebo
    • Index
    • Terminology
    • GUI
    • World Frame and Axis
    • Cookbook
    • Page 1
  • Programming in Robotics
    • C++
      • CMake
    • Python
    • Rust
  • Mathematics in Robotics
    • Linear Algebra
    • Matrix Properties
    • Probability
      • Expectation-Maximization Algorithm
    • Multivariable Function and Derivatives
  • Physics in Robotics
  • Control of Dynamic Systems
    • Dynamic Response and Transfer Function
    • Block Diagram
    • PID Controller
  • Robot Modeling and Control
    • Rotation and Homogeneous Transformation
  • Probabilistic Robotics
    • Bayes Filter
    • Kalman Filter
    • Particle Filter
    • Discrete Bayes Filter
    • Motion Model
    • Perception Model
    • Localization
    • SLAM
  • Miscellany
  • Concept Index
    • Quaternions
Powered by GitBook
On this page
  • Related Readings
  • Introduction
  • A Few Words on Concurrency
  • Experiment Setup
  • Spin Once and Spin Some
  • Experiment 1
  • Experiment 2
  • Experiment 3
  • Spin Until Future is Complete
  • Experiment 4
  • Spin
  • Download Code
  1. ROS2
  2. Tutorials

Executor and Spin Explained

PreviousSubscription and Message Filters DemoNextLifecycle Node Demo

Last updated 1 year ago

This tutorial is based on ROS2 Humble and the behavior described in this tutorial is subject to change in future releases.

Related Readings

Introduction

In this tutorial, we aim to clarify the difference between spin_once, spin_some, spin_until_future_complete, and spin. However, due to limited official documentation, the accuracy of our explanations and interpretations is not guaranteed. We've include code at the end of the tutorial for you to independently verify the behaviors. Please note, this content is specifically tailored to ROS2 Humble; other version may exhibit different behaviors.

A Few Words on Concurrency

It's highly recommended to read the discussion .

By default, ROS 2 is thread safe:

  • Each node's callbacks are called mutually exclusive (i.e. only one callback per node is called at a time, there is no concurrence within a node),

  • Different nodes' callbacks can be executed concurrently:

    • If they run in different processes, or

    • If they are in the same process and spun by a MultiThreadedExecutor, or

    • If they are in the same process and you use multiple threads each running a SingleThreadedExecutor,

    But even in those cases, the active callbacks cannot access another nodes' data, and only one callback per node is being executed, so there are no concurrency issues.

In this tutorial, we will not focus on the callback group.

Experiment Setup

The experimental system consists of

  • A publisher that simultaneously publishes the same messages to two different topics.

  • A subscriber that subscribes to these two topics. The callback function is used to simulate the task execution and the execution time can be adjusted via a parameter.

  • Parameters that control the start-up time of the publisher and subscriber. If the subscriber activates before the publisher, the the executor queue is empty because no messages are published yet. Conversely, if the publisher starts first, the subscriber may see a non-empty queue.

  • Parameters that control the message publication rate and task execution time. An execution time longer than the publication interval leads to buildup of tasks in the executor queue. Conversely, if the execution time is shorter, the queue gets drained.

Spin Once and Spin Some

spin_once and spin_some have similar behaviors. Upon invocation, the executor inspects the work queue. If the queue is empty, the function returns immediately. If tasks are present, spin_once executes a single task, whereas spin_some can handle one or more tasks. It's important to note that although new tasks may arrive during execution, the execution will not address them in the current cycle since task collection occurs only once.

Experiment 1

In this experiment, we demonstrate the call returns immediately if there is no work in the queue. We will introduce a delay in starting the publisher. Consequently, when the subscriber is initiated, no messages have been published yet, resulting in an empty work queue.

First, let's launch the system with spin_once:

ros2 launch bringup spin_once_repeat.launch.py publisher_warmup_time:=10 publication_interval:=1 subscriber_warmup_time:=1 task_running_time:=5

Here is the output of the program:

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [104870]
[INFO] [spin_once_repeat-2]: process started with pid [104872]
[spin_once_repeat-2] [INFO] [1703721523.286740357] [subscriber-main-thread]: ----- start to create the subscriber node
[spin_once_repeat-2] [INFO] [1703721524.292396398] [subscriber-main-thread]: ----- subscriber node is created
[spin_once_repeat-2] [INFO] [1703721524.292595346] [subscriber-main-thread]: ----- start spin_once
[spin_once_repeat-2] [INFO] [1703721524.292609020] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721525.292906999] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721526.293163103] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721527.293488979] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721528.293822772] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721529.294154032] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721530.294414491] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721531.294735447] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703721532.294992828] [subscriber-main-thread]: ----- call spin_once
[publisher-1] [INFO] [1703721533.290849977] [task_publisher]: creating publisher and timer
[spin_once_repeat-2] [INFO] [1703721533.295210474] [subscriber-main-thread]: ----- call spin_once
[publisher-1] [INFO] [1703721534.292651668] [task_publisher]: Publishing: 'task 1'
[spin_once_repeat-2] [INFO] [1703721534.295465766] [subscriber-main-thread]: ----- spin_once ends
[INFO] [spin_once_repeat-2]: process has finished cleanly [pid 104872]
[publisher-1] [INFO] [1703721535.292633903] [task_publisher]: Publishing: 'task 2'
[publisher-1] [INFO] [1703721536.292643748] [task_publisher]: Publishing: 'task 3'
[publisher-1] [INFO] [1703721537.292647627] [task_publisher]: Publishing: 'task 4'
[publisher-1] [INFO] [1703721538.292668753] [task_publisher]: Publishing: 'task 5'

The log message confirms that the subscriber is created before the first message is published. Moreover, the spin_once call returns immediately.

Let's try spin_some:

ros2 launch bringup spin_some_repeat.launch.py publisher_warmup_time:=10 publication_interval:=1 subscriber_warmup_time:=1 task_running_time:=5

Similar results are produced:

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [106002]
[INFO] [spin_some_repeat-2]: process started with pid [106004]
[spin_some_repeat-2] [INFO] [1703721861.285776631] [subscriber-main-thread]: ----- start to create the subscriber node
[spin_some_repeat-2] [INFO] [1703721862.291873202] [subscriber-main-thread]: ----- subscriber node is created
[spin_some_repeat-2] [INFO] [1703721862.292095052] [subscriber-main-thread]: ----- start spin_some
[spin_some_repeat-2] [INFO] [1703721862.292110432] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721863.292420633] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721864.292675445] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721865.292924385] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721866.293178492] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721867.293437536] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721868.293725006] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721869.293870219] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703721870.294132843] [subscriber-main-thread]: ----- call spin_some
[publisher-1] [INFO] [1703721871.288962120] [task_publisher]: creating publisher and timer
[spin_some_repeat-2] [INFO] [1703721871.294388290] [subscriber-main-thread]: ----- call spin_some
[publisher-1] [INFO] [1703721872.290757235] [task_publisher]: Publishing: 'task 1'
[spin_some_repeat-2] [INFO] [1703721872.294619692] [subscriber-main-thread]: ----- spin_some ends
[INFO] [spin_some_repeat-2]: process has finished cleanly [pid 106004]
[publisher-1] [INFO] [1703721873.290692259] [task_publisher]: Publishing: 'task 2'
[publisher-1] [INFO] [1703721874.290768198] [task_publisher]: Publishing: 'task 3'

Experiment 2

In this experiment, we demonstrate the difference between spin_once and spin_some. Specifically, spin_once only processes a single task in the queue while spin_some can handle multiple tasks. To illustrate this, we start the publisher before the subscribing, ensuring that by the time the subscriber is initiated, messages have already been published to the topics. Additionally, the message publication rate is set to be slower than the rate at which tasks can be competed.

Let's start with spin_once:

ros2 launch bringup spin_once_repeat.launch.py publisher_warmup_time:=1 publication_interval:=3 subscriber_warmup_time:=10 task_running_time:=1

As we can see in the output below, between two calls of spin_once, at most one callback is executed:

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [106255]
[INFO] [spin_once_repeat-2]: process started with pid [106257]
[spin_once_repeat-2] [INFO] [1703722264.902515185] [subscriber-main-thread]: ----- start to create the subscriber node
[publisher-1] [INFO] [1703722265.906442638] [task_publisher]: creating publisher and timer
[publisher-1] [INFO] [1703722268.908613701] [task_publisher]: Publishing: 'task 1'
[publisher-1] [INFO] [1703722271.908610507] [task_publisher]: Publishing: 'task 2'
[spin_once_repeat-2] [INFO] [1703722274.906863820] [subscriber-main-thread]: ----- subscriber node is created
[spin_once_repeat-2] [INFO] [1703722274.907071708] [subscriber-main-thread]: ----- start spin_once
[spin_once_repeat-2] [INFO] [1703722274.907087350] [subscriber-main-thread]: ----- call spin_once
[publisher-1] [INFO] [1703722274.908598651] [task_publisher]: Publishing: 'task 3'
[spin_once_repeat-2] [INFO] [1703722275.907374781] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703722276.907591243] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703722276.907733630] [task_subscriber]: start the task task 3
[spin_once_repeat-2] [INFO] [1703722277.907985576] [task_subscriber]: task is complete task 3
[publisher-1] [INFO] [1703722277.908628763] [task_publisher]: Publishing: 'task 4'
[spin_once_repeat-2] [INFO] [1703722278.908218273] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703722278.908302400] [task_subscriber]: start the task task 3
[spin_once_repeat-2] [INFO] [1703722279.908425731] [task_subscriber]: task is complete task 3
[publisher-1] [INFO] [1703722280.908625705] [task_publisher]: Publishing: 'task 5'
[spin_once_repeat-2] [INFO] [1703722280.908680301] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703722281.908934264] [subscriber-main-thread]: ----- call spin_once
[spin_once_repeat-2] [INFO] [1703722281.909057961] [task_subscriber]: start the task task 4
[spin_once_repeat-2] [INFO] [1703722282.909210972] [task_subscriber]: task is complete task 4
[publisher-1] [INFO] [1703722283.908633067] [task_publisher]: Publishing: 'task 6'

Now let's check the behavior of spin_some:

ros2 launch bringup spin_some_repeat.launch.py publisher_warmup_time:=1 publication_interval:=3 subscriber_warmup_time:=10 task_running_time:=1

Here is the output:

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [106414]
[INFO] [spin_some_repeat-2]: process started with pid [106416]
[spin_some_repeat-2] [INFO] [1703722411.352279678] [subscriber-main-thread]: ----- start to create the subscriber node
[publisher-1] [INFO] [1703722412.355485901] [task_publisher]: creating publisher and timer
[publisher-1] [INFO] [1703722415.357177825] [task_publisher]: Publishing: 'task 1'
[publisher-1] [INFO] [1703722418.357209205] [task_publisher]: Publishing: 'task 2'
[spin_some_repeat-2] [INFO] [1703722421.357183365] [subscriber-main-thread]: ----- subscriber node is created
[publisher-1] [INFO] [1703722421.357189710] [task_publisher]: Publishing: 'task 3'
[spin_some_repeat-2] [INFO] [1703722421.357378437] [subscriber-main-thread]: ----- start spin_some
[spin_some_repeat-2] [INFO] [1703722421.357391962] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703722422.357763617] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703722422.357974239] [task_subscriber]: start the task task 3
[spin_some_repeat-2] [INFO] [1703722423.358207789] [task_subscriber]: task is complete task 3
[spin_some_repeat-2] [INFO] [1703722423.358434326] [task_subscriber]: start the task task 3
[publisher-1] [INFO] [1703722424.357224667] [task_publisher]: Publishing: 'task 4'
[spin_some_repeat-2] [INFO] [1703722424.358609446] [task_subscriber]: task is complete task 3
[spin_some_repeat-2] [INFO] [1703722425.358905006] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703722425.359112045] [task_subscriber]: start the task task 4
[spin_some_repeat-2] [INFO] [1703722426.359259373] [task_subscriber]: task is complete task 4
[spin_some_repeat-2] [INFO] [1703722426.359453598] [task_subscriber]: start the task task 4

Let's examine the log more closely. The snippet below shows that between two spin_some calls, two task 3 are executed. This is expected since the same message is published to two topics. This shows spin_some can execute multiple tasks.

Interestingly, despite using a multi-thread executor, the two tasks are executed sequentially. This behavior suggests that the system operates as though only a single thread is allocated to the node.

We also notice that the interval between the two spin_some call is approximately 2 seconds, aligning with the combined execution time of two tasks, where each takes about 1 second. This observation highlights the "blocking" nature of the spin_some method.

[spin_some_repeat-2] [INFO] [1703722422.357763617] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703722422.357974239] [task_subscriber]: start the task task 3
[spin_some_repeat-2] [INFO] [1703722423.358207789] [task_subscriber]: task is complete task 3
[spin_some_repeat-2] [INFO] [1703722423.358434326] [task_subscriber]: start the task task 3
[publisher-1] [INFO] [1703722424.357224667] [task_publisher]: Publishing: 'task 4'
[spin_some_repeat-2] [INFO] [1703722424.358609446] [task_subscriber]: task is complete task 3
[spin_some_repeat-2] [INFO] [1703722425.358905006] [subscriber-main-thread]: ----- call spin_some

Experiment 3

This experiment, focused on spin_some, demonstrates that the executor collects work only once per spin_some call. We configure tasks to run for 3 seconds and publish messages every 1 second. Given that work arrives faster than it can be processed, we will see accumulation of tasks in the queue.

Launch the system using the following command:

ros2 launch bringup spin_some_repeat.launch.py publisher_warmup_time:=1 publication_interval:=1 subscriber_warmup_time:=10 task_running_time:=3

It produces the following outputs: (the key is that there are multiple call spin_some messages)

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [107142]
[INFO] [spin_some_repeat-2]: process started with pid [107144]
[spin_some_repeat-2] [INFO] [1703723176.275294226] [subscriber-main-thread]: ----- start to create the subscriber node
[publisher-1] [INFO] [1703723177.279028661] [task_publisher]: creating publisher and timer
[publisher-1] [INFO] [1703723178.280777602] [task_publisher]: Publishing: 'task 1'
[publisher-1] [INFO] [1703723179.280792555] [task_publisher]: Publishing: 'task 2'
[publisher-1] [INFO] [1703723180.280780229] [task_publisher]: Publishing: 'task 3'
[publisher-1] [INFO] [1703723181.280840609] [task_publisher]: Publishing: 'task 4'
[publisher-1] [INFO] [1703723182.280792853] [task_publisher]: Publishing: 'task 5'
[publisher-1] [INFO] [1703723183.280864991] [task_publisher]: Publishing: 'task 6'
[publisher-1] [INFO] [1703723184.280812151] [task_publisher]: Publishing: 'task 7'
[publisher-1] [INFO] [1703723185.280807081] [task_publisher]: Publishing: 'task 8'
[spin_some_repeat-2] [INFO] [1703723186.280558357] [subscriber-main-thread]: ----- subscriber node is created
[spin_some_repeat-2] [INFO] [1703723186.280754425] [subscriber-main-thread]: ----- start spin_some
[spin_some_repeat-2] [INFO] [1703723186.280769249] [subscriber-main-thread]: ----- call spin_some
[publisher-1] [INFO] [1703723186.280767472] [task_publisher]: Publishing: 'task 9'
[publisher-1] [INFO] [1703723187.280827878] [task_publisher]: Publishing: 'task 10'
[spin_some_repeat-2] [INFO] [1703723187.281134662] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703723187.281333990] [task_subscriber]: start the task task 9
[publisher-1] [INFO] [1703723188.280855246] [task_publisher]: Publishing: 'task 11'
[publisher-1] [INFO] [1703723189.280850277] [task_publisher]: Publishing: 'task 12'
[publisher-1] [INFO] [1703723190.280844620] [task_publisher]: Publishing: 'task 13'
[spin_some_repeat-2] [INFO] [1703723190.281558348] [task_subscriber]: task is complete task 9
[spin_some_repeat-2] [INFO] [1703723190.281732216] [task_subscriber]: start the task task 11
[publisher-1] [INFO] [1703723191.280854109] [task_publisher]: Publishing: 'task 14'
[publisher-1] [INFO] [1703723192.280859971] [task_publisher]: Publishing: 'task 15'
[publisher-1] [INFO] [1703723193.280857388] [task_publisher]: Publishing: 'task 16'
[spin_some_repeat-2] [INFO] [1703723193.281838492] [task_subscriber]: task is complete task 11
[publisher-1] [INFO] [1703723194.280880210] [task_publisher]: Publishing: 'task 17'
[spin_some_repeat-2] [INFO] [1703723194.282059193] [subscriber-main-thread]: ----- call spin_some
[spin_some_repeat-2] [INFO] [1703723194.282234788] [task_subscriber]: start the task task 15

Spin Until Future is Complete

In the previous section, we observed that both spin_once and spin_some methods return either when the queue is empty or after the collected work is done. In particular, experiment 3 shows that spin_some returns even when there is more work in the queue. You may wonder how can we keep the executor continuously working on new tasks in the queue?

One approach involves manually wrapping the spin_once and spin_some methods within a loop. Alternatively, you can use the built-in methods spin_until_future_complete or spin. The difference between spin_until_future_complete and spin methods is that the "focus time" is bounded by the future argument in spin_until_future_complete whereas the "focus time" is unbounded in spin.

Experiment 4

To launch the experiment, use the command below:

ros2 launch bringup spin_until_future_complete.launch.py publisher_warmup_time:=1 publication_interval:=2 subscriber_warmup_time:=5 task_running_time:=1

The output of the program is presented as follows:

[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [112108]
[INFO] [spin_until_future_complete-2]: process started with pid [112110]
[spin_until_future_complete-2] [INFO] [1703727953.315024507] [subscriber-main-thread]: ----- start to create the subscriber node
[publisher-1] [INFO] [1703727954.318600781] [task_publisher]: creating publisher and timer
[publisher-1] [INFO] [1703727956.320320568] [task_publisher]: Publishing: 'task 1'
[spin_until_future_complete-2] [INFO] [1703727958.320232632] [subscriber-main-thread]: ----- subscriber node is created
[publisher-1] [INFO] [1703727958.320275752] [task_publisher]: Publishing: 'task 2'
[spin_until_future_complete-2] [INFO] [1703727958.320647246] [subscriber-main-thread]: ----- start spin_some
[spin_until_future_complete-2] [INFO] [1703727958.320657151] [subscriber-main-thread]: doing some work [0]
[spin_until_future_complete-2] [INFO] [1703727958.320914234] [task_subscriber]: start the task task 2
[spin_until_future_complete-2] [INFO] [1703727959.321089551] [task_subscriber]: task is complete task 2
[spin_until_future_complete-2] [INFO] [1703727959.321303642] [task_subscriber]: start the task task 2
[publisher-1] [INFO] [1703727960.320299603] [task_publisher]: Publishing: 'task 3'
[spin_until_future_complete-2] [INFO] [1703727960.321402142] [task_subscriber]: task is complete task 2
[spin_until_future_complete-2] [INFO] [1703727960.321625114] [task_subscriber]: start the task task 3
[spin_until_future_complete-2] [INFO] [1703727961.321797323] [task_subscriber]: task is complete task 3
[spin_until_future_complete-2] [INFO] [1703727961.321989861] [task_subscriber]: start the task task 3
[publisher-1] [INFO] [1703727962.320294331] [task_publisher]: Publishing: 'task 4'
[spin_until_future_complete-2] [INFO] [1703727962.322159138] [task_subscriber]: task is complete task 3
[spin_until_future_complete-2] [INFO] [1703727962.322412436] [task_subscriber]: start the task task 4
[spin_until_future_complete-2] [INFO] [1703727963.320893505] [subscriber-main-thread]: doing some work [1]
[spin_until_future_complete-2] [INFO] [1703727963.322512534] [task_subscriber]: task is complete task 4
[spin_until_future_complete-2] [INFO] [1703727963.322717466] [task_subscriber]: start the task task 4
[publisher-1] [INFO] [1703727964.320283152] [task_publisher]: Publishing: 'task 5'
[spin_until_future_complete-2] [INFO] [1703727964.322817282] [task_subscriber]: task is complete task 4
[spin_until_future_complete-2] [INFO] [1703727964.323053314] [task_subscriber]: start the task task 5
[spin_until_future_complete-2] [INFO] [1703727965.323226421] [task_subscriber]: task is complete task 5
[spin_until_future_complete-2] [INFO] [1703727965.323427751] [task_subscriber]: start the task task 5
[publisher-1] [INFO] [1703727966.320294448] [task_publisher]: Publishing: 'task 6'
[spin_until_future_complete-2] [INFO] [1703727966.323529903] [task_subscriber]: task is complete task 5
[spin_until_future_complete-2] [INFO] [1703727966.323760117] [task_subscriber]: start the task task 6
[spin_until_future_complete-2] [INFO] [1703727967.323863154] [task_subscriber]: task is complete task 6
[spin_until_future_complete-2] [INFO] [1703727967.324076546] [task_subscriber]: start the task task 6
[publisher-1] [INFO] [1703727968.320294840] [task_publisher]: Publishing: 'task 7'
[spin_until_future_complete-2] [INFO] [1703727968.321037042] [subscriber-main-thread]: doing some work [2]
[spin_until_future_complete-2] [INFO] [1703727968.324188134] [task_subscriber]: task is complete task 6
[spin_until_future_complete-2] [INFO] [1703727968.324408263] [task_subscriber]: start the task task 7
[spin_until_future_complete-2] [INFO] [1703727969.324533568] [task_subscriber]: task is complete task 7
[spin_until_future_complete-2] [INFO] [1703727969.324728485] [task_subscriber]: start the task task 7
[publisher-1] [INFO] [1703727970.320293347] [task_publisher]: Publishing: 'task 8'
[spin_until_future_complete-2] [INFO] [1703727970.324830376] [task_subscriber]: task is complete task 7
[spin_until_future_complete-2] [INFO] [1703727970.325058938] [task_subscriber]: start the task task 8
[spin_until_future_complete-2] [INFO] [1703727971.325161432] [task_subscriber]: task is complete task 8
[spin_until_future_complete-2] [INFO] [1703727971.325351216] [task_subscriber]: start the task task 8
[publisher-1] [INFO] [1703727972.320277252] [task_publisher]: Publishing: 'task 9'
[spin_until_future_complete-2] [INFO] [1703727972.325452600] [task_subscriber]: task is complete task 8
[spin_until_future_complete-2] [INFO] [1703727972.325691728] [task_subscriber]: start the task task 9
[spin_until_future_complete-2] [INFO] [1703727973.321177505] [subscriber-main-thread]: ----- !!! ----- all the side word is done.
[spin_until_future_complete-2] [INFO] [1703727973.325863001] [task_subscriber]: task is complete task 9
[spin_until_future_complete-2] [INFO] [1703727973.326011324] [subscriber-main-thread]: ----- spin_some ends
[INFO] [spin_until_future_complete-2]: process has finished cleanly [pid 112110]
[publisher-1] [INFO] [1703727974.320284937] [task_publisher]: Publishing: 'task 10'
[publisher-1] [INFO] [1703727976.320347662] [task_publisher]: Publishing: 'task 11'

The key part in the output is highlighted in the section below:

[spin_until_future_complete-2] [INFO] [1703727972.325452600] [task_subscriber]: task is complete task 8
[spin_until_future_complete-2] [INFO] [1703727972.325691728] [task_subscriber]: start the task task 9
[spin_until_future_complete-2] [INFO] [1703727973.321177505] [subscriber-main-thread]: ----- !!! ----- all the side word is done.
[spin_until_future_complete-2] [INFO] [1703727973.325863001] [task_subscriber]: task is complete task 9
[spin_until_future_complete-2] [INFO] [1703727973.326011324] [subscriber-main-thread]: ----- spin_some ends

It shows that the spin_until_future_complete exits when the work in the future is done.

Spin

spin is the most common one among these variants. According to the code comments, it does work periodically as it becomes available to the executor. It's a blocking call and may block indefinitely. Unlike other method, the spin method does not return when the work queue is empty. It always waits for additional tasks.

Download Code

©2023 - 2024 all rights reserved

In this experiment, we run a long-running task in a future and pass it to the spin_until_future_complete method. Due to , additional setup is required to wake up the executor, allowing the executor to exit from the spin_until_future_complete method.

The code is available for download at this .

🛒
rclcpp::Executor class reference
Functional difference between spin, spin_once, and spin_until_future_complete
Github discussion on spin function names
Concurrency and thread safety in ROS2
ROS2 document - Callback Groups
Issue - spin_until_future_complete may block forever if nothing wakes the executor after the future completes
ROS2: multi nodes, each on a thread in same process
a pending issue
link