Repository: ros/ros_comm

Issue:
rosbag leaves an ".active" file dangling when receiving SIGTERM
Currently, rosbag leaves a dangling ".active" file when stopped with SIGTERM.

1. Can we update `rosbag` so that the behavior of SIGTERM and SIGINT is the same?
2. Would it be acceptable to backport this behavior to Kinetic, and Melodic?



Discussion:
Extra note: SIGINT on the main process has the same behavior. SIGINT on the process group works as expected
Please consider to contribute a pull request for this.

> Would it be acceptable to backport this behavior to Kinetic, and Melodic?

That depends on the patch necessary. The intended behavior change (to clean up a dangling file) sounds reasonable.

Resolving patch:
diff --git a/tools/rosbag/src/rosbag/rosbag_main.py b/tools/rosbag/src/rosbag/rosbag_main.py
--- a/tools/rosbag/src/rosbag/rosbag_main.py
+++ b/tools/rosbag/src/rosbag/rosbag_main.py
@@ -72,6 +72,12 @@ def _stop_process(signum, frame, old_handler, process):
         old_handler(signum, frame)
 
 
+def _send_process_sigint(signum, frame, old_handler, process):
+    process.send_signal(signal.SIGINT)
+    if old_handler:
+        old_handler(signum, frame)
+
+
 def record_cmd(argv):
     parser = optparse.OptionParser(usage="rosbag record TOPIC1 [TOPIC2 TOPIC3 ...]",
                                    description="Record a bag file with the contents of specified topics.",
@@ -143,6 +149,12 @@ def record_cmd(argv):
         signal.SIGTERM,
         lambda signum, frame: _stop_process(signum, frame, old_handler, process)
     )
+
+    old_handler = signal.signal(
+        signal.SIGINT,
+        lambda signum, frame: _send_process_sigint(signum, frame, old_handler, process)
+    )
+
     # Better way of handling it than os.execv
     # This makes sure stdin handles are passed to the process.
     process = subprocess.Popen(cmd)


Test patch:
diff --git a/test/test_rosbag/CMakeLists.txt b/test/test_rosbag/CMakeLists.txt
--- a/test/test_rosbag/CMakeLists.txt
+++ b/test/test_rosbag/CMakeLists.txt
@@ -50,6 +50,8 @@ if(CATKIN_ENABLE_TESTING)
   add_rostest(${PROJECT_BINARY_DIR}/test/latched_sub.test)
   add_rostest(test/record_two_publishers.test)
   add_rostest(test/record_one_publisher_two_topics.test)
+  add_rostest(test/record_sigint_cleanup.test)
+  add_rostest(test/record_sigterm_cleanup.test)
 
   include_directories(${GTEST_INCLUDE_DIRS})
   add_executable(double_pub EXCLUDE_FROM_ALL test/double_pub.cpp)
diff --git a/test/test_rosbag/test/record_sigint_cleanup.py b/test/test_rosbag/test/record_sigint_cleanup.py
new file mode 100755
--- /dev/null
+++ b/test/test_rosbag/test/record_sigint_cleanup.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2020 Amazon.com, Inc. or its affiliates.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#  * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#  * Neither the name of Willow Garage, Inc. nor the names of its
+#    contributors may be used to endorse or promote products derived
+#    from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import roslib
+roslib.load_manifest('rosbag')
+
+import os
+import unittest
+import rostest
+import sys
+import signal
+from record_signal_cleanup_helper import test_signal_cleanup
+
+TEST_BAG_FILE_NAME = '/tmp/record_sigint_cleanup_test.bag'
+
+class RecordSigintCleanup(unittest.TestCase):
+
+  def test_sigint_cleanup(self):
+    """
+    Test that rosbag cleans up after handling SIGINT
+    """
+    test_signal_cleanup(TEST_BAG_FILE_NAME, signal.SIGINT)
+
+    # check that the recorded file is no longer active
+    self.assertTrue(os.path.isfile(TEST_BAG_FILE_NAME))
+    self.assertFalse(os.path.isfile(TEST_BAG_FILE_NAME+ '.active'))
+
+
+if __name__ == '__main__':
+  rostest.unitrun('test_rosbag', 'test_sigint_cleanup', RecordSigintCleanup, sys.argv)
diff --git a/test/test_rosbag/test/record_sigint_cleanup.test b/test/test_rosbag/test/record_sigint_cleanup.test
new file mode 100644
--- /dev/null
+++ b/test/test_rosbag/test/record_sigint_cleanup.test
@@ -0,0 +1,5 @@
+<launch>
+  <node pkg="rostopic" type="rostopic" name="rostopic_pub1"
+        args="pub -r 10 chatter std_msgs/String chatter1"/>
+  <test test-name="test_sigint_cleanup" pkg="test_rosbag" type="record_sigint_cleanup.py"/>
+</launch>
diff --git a/test/test_rosbag/test/record_signal_cleanup_helper.py b/test/test_rosbag/test/record_signal_cleanup_helper.py
new file mode 100644
--- /dev/null
+++ b/test/test_rosbag/test/record_signal_cleanup_helper.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2020 Amazon.com, Inc. or its affiliates.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#  * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#  * Neither the name of Willow Garage, Inc. nor the names of its
+#    contributors may be used to endorse or promote products derived
+#    from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import time
+import subprocess
+
+RECORD_COMMAND = ['rosbag',
+                  'record',
+                  'chatter',
+                  '-O',
+                  '--duration=5']
+SLEEP_TIME_SEC = 10
+
+def test_signal_cleanup(test_bag_file_name, test_signal):
+    """
+    Run rosbag record and send a signal to it after some time.
+
+    :param test_bag_file_name: bag name for recorded output
+    :param test_signal: signal to send to rosbag
+    """
+    test_command = list(RECORD_COMMAND)
+    test_command.insert(4, test_bag_file_name)
+
+    p = subprocess.Popen(test_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    # wait while the recorder creates a bag for us to examine
+    time.sleep(SLEEP_TIME_SEC)
+    p.send_signal(test_signal)
+    p.wait()
diff --git a/test/test_rosbag/test/record_sigterm_cleanup.py b/test/test_rosbag/test/record_sigterm_cleanup.py
new file mode 100755
--- /dev/null
+++ b/test/test_rosbag/test/record_sigterm_cleanup.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2020 Amazon.com, Inc. or its affiliates.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#  * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#  * Neither the name of Willow Garage, Inc. nor the names of its
+#    contributors may be used to endorse or promote products derived
+#    from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import roslib
+roslib.load_manifest('rosbag')
+
+import os
+import unittest
+import rostest
+import sys
+import signal
+from record_signal_cleanup_helper import test_signal_cleanup
+
+TEST_BAG_FILE_NAME = '/tmp/record_sigterm_cleanup_test.bag'
+
+class RecordSigtermCleanup(unittest.TestCase):
+
+  def test_sigterm_cleanup(self):
+    """
+    Test that rosbag cleans up after handling SIGTERM
+    """
+    test_signal_cleanup(TEST_BAG_FILE_NAME, signal.SIGTERM)
+
+    # check that the recorded file is no longer active
+    self.assertTrue(os.path.isfile(TEST_BAG_FILE_NAME))
+    self.assertFalse(os.path.isfile(TEST_BAG_FILE_NAME+ '.active'))
+
+
+if __name__ == '__main__':
+  rostest.unitrun('test_rosbag', 'test_sigterm_cleanup', RecordSigtermCleanup, sys.argv)
diff --git a/test/test_rosbag/test/record_sigterm_cleanup.test b/test/test_rosbag/test/record_sigterm_cleanup.test
new file mode 100644
--- /dev/null
+++ b/test/test_rosbag/test/record_sigterm_cleanup.test
@@ -0,0 +1,5 @@
+<launch>
+  <node pkg="rostopic" type="rostopic" name="rostopic_pub1"
+        args="pub -r 10 chatter std_msgs/String chatter1"/>
+  <test test-name="test_sigterm_cleanup" pkg="test_rosbag" type="record_sigterm_cleanup.py"/>
+</launch>