Repository: ros/ros_comm

Issue:
rospy.sleep(): request for specific exception if time moved backwards
I kindly request for the TODO of rospy.sleep() in [rospy.timer.py](https://github.com/ros/ros_comm/blob/hydro-devel/clients/rospy/src/rospy/timer.py) to be implemented and use a specific exception if ros time moved backwards.
I had trouble to find the cause of a python node shutting down, when using 'rosbag play -l --clock bagfile'. When the implementation follows the ros tutorials on python the ROSInterruptException is always catched:

``` python
if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException: pass
```

And there is no error message telling you, that time moved backwards.



Discussion:
Since it is not very likely that this will get implemented by the maintainer I have marked the issue with the milestone `untargeted`.

Please consider to provide a pull request for the proposed feature. One comment though: a newly introduced exception would anyway need to inherit from `rospy.ROSInterruptException` in order to not change the API in terms of raising a new unrelated exception.

I made pull request #491 for the proposed feature. In addition to the exception inheriting from rospy.ROSInterruptException a logerr message is produced to inform the user. I hope this is ok. I looked into [rospy.topics.py](https://github.com/ros/ros_comm/blob/hydro-devel/clients/rospy/src/rospy/topics.py) which produces log messages, too.


Resolving patch:
diff --git a/clients/rospy/src/rospy/exceptions.py b/clients/rospy/src/rospy/exceptions.py
--- a/clients/rospy/src/rospy/exceptions.py
+++ b/clients/rospy/src/rospy/exceptions.py
@@ -61,6 +61,15 @@ class ROSInterruptException(ROSException, KeyboardInterrupt):
     """    
     pass
 
+class ROSTimeMovedBackwardsException(ROSInterruptException): 
+    """
+    Exception if time moved backwards
+    """
+    def __init__(self, time):
+        self.time = time
+        """The amount of time in seconds."""
+        super(ROSTimeMovedBackwardsException, self).__init__("ROS time moved backwards")
+
 class ROSInternalException(Exception):
     """
     Base class for exceptions that are internal to the ROS system
diff --git a/clients/rospy/src/rospy/timer.py b/clients/rospy/src/rospy/timer.py
--- a/clients/rospy/src/rospy/timer.py
+++ b/clients/rospy/src/rospy/timer.py
@@ -65,8 +65,10 @@ def sleep(self):
         account the time elapsed since the last successful
         sleep().
         
-        @raise ROSInterruptException: if ROS time is set backwards or if
-        ROS shutdown occurs before sleep completes
+        @raise ROSInterruptException: if ROS shutdown occurs before
+        sleep completes
+        @raise ROSTimeMovedBackwardsException: if ROS time is set
+        backwards
         """
         curr_time = rospy.rostime.get_rostime()
         # detect time jumping backwards
@@ -83,7 +85,6 @@ def sleep(self):
         if curr_time - self.last_time > self.sleep_dur * 2:
             self.last_time = curr_time
 
-# TODO: may want more specific exceptions for sleep
 def sleep(duration):
     """
     sleep for the specified duration in ROS time. If duration
@@ -91,8 +92,10 @@ def sleep(duration):
     
     @param duration: seconds (or rospy.Duration) to sleep
     @type  duration: float or Duration
-    @raise ROSInterruptException: if ROS time is set backwards or if
-    ROS shutdown occurs before sleep completes
+    @raise ROSInterruptException: if ROS shutdown occurs before sleep
+    completes
+    @raise ROSTimeMovedBackwardsException: if ROS time is set
+    backwards
     """
     if rospy.rostime.is_wallclock():
         if isinstance(duration, genpy.Duration):
@@ -128,7 +131,9 @@ def sleep(duration):
                 rostime_cond.wait(0.5)
 
         if rospy.rostime.get_rostime() < initial_rostime:
-            raise rospy.exceptions.ROSInterruptException("ROS time moved backwards")
+            time_jump = (initial_rostime - rospy.rostime.get_rostime()).to_sec()
+            rospy.core.logerr("ROS time moved backwards: %ss", time_jump)
+            raise rospy.exceptions.ROSTimeMovedBackwardsException(time_jump)
         if rospy.core.is_shutdown():
             raise rospy.exceptions.ROSInterruptException("ROS shutdown request")
 


Test patch:
diff --git a/test/test_rospy/test/unit/test_rospy_exceptions.py b/test/test_rospy/test/unit/test_rospy_exceptions.py
--- a/test/test_rospy/test/unit/test_rospy_exceptions.py
+++ b/test/test_rospy/test/unit/test_rospy_exceptions.py
@@ -62,3 +62,16 @@ def test_ROSInterruptException(self):
             raise ROSInterruptException("test")
         except KeyboardInterrupt:
             pass
+
+    def test_ROSTimeMovedBackwardsException(self):
+        from rospy.exceptions import ROSTimeMovedBackwardsException, ROSInterruptException
+        try:
+            raise ROSTimeMovedBackwardsException(1.0)
+        except ROSInterruptException as e:
+            # ensure the message is not changed, because old code may check it
+            self.assertEqual("ROS time moved backwards", e.message)
+        try:
+            time = 1.0
+            raise ROSTimeMovedBackwardsException(time)
+        except ROSTimeMovedBackwardsException as e:
+            self.assertEqual(time, e.time)