0
$\begingroup$

I'd like to have a script that will create/destroy a publisher node. I'm experiencing problems with that, being unable to correctly destroy the node instance. I'm running ROS2 Humble under Windows

I isolated this in a very minimal example, trying to create/destroy a simple existing demo_nodes_py/talker node. The test verifies that "ros2 topic list" sees the /chatter topic created by talker after node is created and verifies it's gone after node is destroyed.

import sys
import os
import unittest
import time
import rclpy
import subprocess
import signal

class TestCreateDestroyNode(unittest.TestCase):
    """Unit test class."""

    def has_topic(self):
        print(f"Checking global topics...")
    
        worked = False
        try:
            print("Using a 5-sec timeout")
            topics = subprocess.check_output( ["ros2","topic","list"], timeout=5 ).decode()
            worked = True
        except subprocess.TimeoutExpired:
            print( "ros2 topic list timed-out" )
            pass
        
        self.assertTrue(worked)
    
        print(f"Found global topics:\n{topics}")
        return (topics.find( "/chatter" ) != -1)

    def test_create_destroy(self):
        
        print( "Verify topic is not present" )
        self.assertTrue( not self.has_topic() )
       
        print("Launching talker")
        process = subprocess.Popen(["ros2","run","demo_nodes_py","talker"], shell=True)
        print("Launched talker")
        
        # Give process a chance to start
        time.sleep(1)
        
        self.assertTrue( self.has_topic() )

        time.sleep(1)
                
        print("Stopping talker")
        process.kill()
        process.wait()
        
        self.assertTrue( not self.has_topic() )
            
if __name__ == '__main__':

    # Run unit test
    unittest.main(__name__)

This does not work, after I tried to kill the process, the last self.assertTrue( not self.has_topic() ) fails and I see /chatter notifications still being displayed in the console.

What's the best/recommended way to create/destroy a node programatically?

$\endgroup$
3
  • $\begingroup$ Instead of killing the process, I suggest using ROS 2 Node API. For example, you can call destroy_node followed by shutdown call. The following references can help: 1, 2, 3, 4 $\endgroup$
    – ravi
    Commented Dec 20, 2023 at 4:44
  • $\begingroup$ @ravi: But destroy_node needs a Node instance, and I have none as I created the node through a subprocess. I cannot find how to get the Node reference in this case. My Node runs in a different process. $\endgroup$
    – jpo38
    Commented Dec 20, 2023 at 7:59
  • $\begingroup$ If thats the case, sending a SIGINT signal should be the way as suggested by @Mike973 in the answer section $\endgroup$
    – ravi
    Commented Dec 20, 2023 at 23:36

2 Answers 2

0
$\begingroup$

You should not use python's process.kill() like you are, which sends a SIGKILL OS signal to the process. On linux, you should instead send a SIGINT.

Success is also going to depend on the node being written to catch the exception and shutdown cleanly. The talker.py demo code you are using looks correct to me.

Note: a co-worker informs me that on MS-Windows there are severe restrictions on what signals can be sent to another process, and that the python signal name is different from SIGINT (but he didn't remember what it should be.)

$\endgroup$
2
  • $\begingroup$ OK, have you tried this? How do you suggest to send SIGINT? Because I tried to replace process.kill() by process.send_signal( signal.SIGINT ) and I get the exception ValueError("Unsupported signal: {}".format(sig)). I also tried process.send_signal( signal.SIGINT ), this has no effect, the process still runs. $\endgroup$
    – jpo38
    Commented Dec 21, 2023 at 13:09
  • $\begingroup$ Updated again. Apparently there are ugly differences between linux and windows platforms in this area. $\endgroup$
    – Mike973
    Commented Dec 21, 2023 at 17:39
0
$\begingroup$

I found a way to "cleanly" exit the created node process using process.send_signal( signal.CTRL_C_EVENT ) before process.kill(). Sinding Ctrl+C makes the node exit cleanly.

Here is the working code:

import sys
import os
import unittest
import time
import rclpy
import subprocess
import signal

class TestCreateDestroyNode(unittest.TestCase):
    """Unit test class."""

    def has_topic(self):
        print(f"Checking global topics...")
    
        worked = False
        try:
            print("Using a 5-sec timeout")
            topics = subprocess.check_output( ["ros2","topic","list"], timeout=5 ).decode()
            worked = True
        except subprocess.TimeoutExpired:
            print( "ros2 topic list timed-out" )
            pass
        
        self.assertTrue(worked)
    
        print(f"Found global topics:\n{topics}")
        return (topics.find( "/chatter" ) != -1)

    def test_create_destroy(self):
        
        print( "Verify topic is not present" )
        self.assertTrue( not self.has_topic() )
       
        print("Launching talker")
        process = subprocess.Popen(["ros2","run","demo_nodes_py","talker"], shell=True)
        print("Launched talker")
        
        # Give process a chance to start
        time.sleep(1)
        
        self.assertTrue( self.has_topic() )

        time.sleep(1)
                
        print("Stopping talker")
        try:
            process.send_signal( signal.CTRL_C_EVENT )
        except KeyboardInterrupt:
            print("Ignoring Ctrl+C")
            pass
        except:
            print("Failed to send Ctrl+C")
            pass

        try:
            process.kill()
        except:
            print("Failed to kill")
            pass

        try:
            process.wait()
        except:
            print("Failed to wait")
            pass
        
        self.assertTrue( not self.has_topic() )
            
if __name__ == '__main__':

    # Run unit test
    unittest.main(__name__)

This test passes. However, I keep this question as unanswered, because I believe there must be an easier and nicer way to achieve this.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.