From 6a9672a26443c901f4a465c86992ecece3f73bbd Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Wed, 18 Oct 2017 16:46:39 -0400 Subject: [PATCH] Make scheduler.Timeout exception hashable The python standard library in Python 3.6.3 and earlier has a bug with handling unhashable exceptions: https://bugs.python.org/issue28603 Although oslo_log will catch the error, make scheduler.Timeout hashable so that all exceptions will be printable. Prior to 640abe0c12e63c207fcf67592838f112a29f5b43 we just used __cmp__(), but that isn't used in Python 3. Defining __eq__(), which is required for the total_ordering decorator, makes the class unhashable in Python 3. Change-Id: Idde65b2d41490ab8318b5a8b95ea74e9b96b4e5c Related-Bug: #1724366 Related-Bug: #1721654 --- heat/engine/scheduler.py | 17 ++++++----------- heat/tests/engine/test_scheduler.py | 6 ++---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/heat/engine/scheduler.py b/heat/engine/scheduler.py index d7b120964c..5c72d13a1f 100644 --- a/heat/engine/scheduler.py +++ b/heat/engine/scheduler.py @@ -11,7 +11,6 @@ # License for the specific language governing permissions and limitations # under the License. -import functools import sys import types @@ -47,7 +46,6 @@ def task_description(task): return encodeutils.safe_decode(repr(task)) -@functools.total_ordering class Timeout(BaseException): """Raised when task has exceeded its allotted (wallclock) running time. @@ -78,14 +76,11 @@ class Timeout(BaseException): generator.close() return False - def __eq__(self, other): - if not isinstance(other, Timeout): - return NotImplemented - return not (self < other or other < self) + def earlier_than(self, other): + if other is None: + return True - def __lt__(self, other): - if not isinstance(other, Timeout): - return NotImplemented + assert isinstance(other, Timeout), "Invalid type for Timeout compare" return self._duration.endtime() < other._duration.endtime() @@ -245,7 +240,7 @@ class TaskRunner(object): else: if timeout is not None: new_timeout = Timeout(self, timeout) - if self._timeout is None or new_timeout < self._timeout: + if new_timeout.earlier_than(self._timeout): self._timeout = new_timeout done = self.step() if resuming else self.done() @@ -281,7 +276,7 @@ class TaskRunner(object): self._runner.close() else: timeout = TimedCancel(self, grace_period) - if self._timeout is None or timeout < self._timeout: + if timeout.earlier_than(self._timeout): self._timeout = timeout def started(self): diff --git a/heat/tests/engine/test_scheduler.py b/heat/tests/engine/test_scheduler.py index 83fea5b66e..68b9657a34 100644 --- a/heat/tests/engine/test_scheduler.py +++ b/heat/tests/engine/test_scheduler.py @@ -1283,10 +1283,8 @@ class TimeoutTest(common.HeatTestCase): eventlet.sleep(0.01) later = scheduler.Timeout(task, 10) - self.assertLess(earlier, later) - self.assertGreater(later, earlier) - self.assertEqual(earlier, earlier) - self.assertNotEqual(earlier, later) + self.assertTrue(earlier.earlier_than(later)) + self.assertFalse(later.earlier_than(earlier)) class DescriptionTest(common.HeatTestCase):