From 2736ea74fcf00a44e518d53064d2a8c8849d71e4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 1 Aug 2016 15:35:27 -0700 Subject: [PATCH] Fix HTTP response hanging if system time set back. Closes #802 --- CHANGES.txt | 4 +++ supervisor/http.py | 12 ++++---- supervisor/tests/test_http.py | 53 ++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f1aa55e5e..6dffa5819 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -31,6 +31,10 @@ - Zope ``trackrefs``, a debugging tool that was included in the ``tests`` directory but hadn't been used for years, has been removed. +- Fixed an issue where `supervisord` could hang when responding to HTTP + requests (including `supervisorctl` commands) if the system time was set + back after `supervisord` was started. + 3.3.0 (2016-05-14) ------------------ diff --git a/supervisor/http.py b/supervisor/http.py index 947c25e36..41884c750 100644 --- a/supervisor/http.py +++ b/supervisor/http.py @@ -334,17 +334,16 @@ class deferring_http_channel(http_server.http_channel): # order to spew tail -f output faster (speculative) ac_out_buffer_size = 4096 - delay = False - writable_check = time.time() + delay = 0 # seconds + last_writable_check = time.time() def writable(self, t=time.time): now = t() if self.delay: # we called a deferred producer via this channel (see refill_buffer) - last_writable_check = self.writable_check - elapsed = now - last_writable_check - if elapsed > self.delay: - self.writable_check = now + elapsed = now - self.last_writable_check + if (elapsed > self.delay) or (elapsed < 0): + self.last_writable_check = now return True else: return False @@ -901,4 +900,3 @@ def __init__(self, dict, handler, realm='default'): auth_handler.__init__(self, dict, handler, realm) # override the authorizer with one that knows about SHA hashes too self.authorizer = encrypted_dictionary_authorizer(dict) - diff --git a/supervisor/tests/test_http.py b/supervisor/tests/test_http.py index 1592ce72a..555943d69 100644 --- a/supervisor/tests/test_http.py +++ b/supervisor/tests/test_http.py @@ -312,7 +312,7 @@ def test_more_noproducer(self): producer = self._makeOne(None, None) self.assertEqual(producer.more(), '') -class Test_deferring_http_request(unittest.TestCase): +class DeferringHttpRequestTests(unittest.TestCase): def _getTargetClass(self): from supervisor.http import deferring_http_request return deferring_http_request @@ -428,6 +428,57 @@ def test_done_http_09(self): inst.done() self.assertTrue(channel.closed) +class DeferringHttpChannelTests(unittest.TestCase): + def _getTargetClass(self): + from supervisor.http import deferring_http_channel + return deferring_http_channel + + def _makeOne(self): + return self._getTargetClass()( + server=None, + conn=None, + addr=None + ) + + def test_defaults_delay_and_last_writable_check_time(self): + channel = self._makeOne() + self.assertEqual(channel.delay, 0) + self.assertTrue(channel.last_writable_check > 0) + + def test_writable_with_delay_is_False_if_elapsed_lt_delay(self): + channel = self._makeOne() + channel.delay = 2 + channel.last_writable_check = _NOW + later = _NOW + 1 + self.assertFalse(channel.writable(t=lambda: later)) + self.assertEqual(channel.last_writable_check, _NOW) + + def test_writable_with_delay_is_False_if_elapsed_eq_delay(self): + channel = self._makeOne() + channel.delay = 2 + channel.last_writable_check = _NOW + later = _NOW + channel.delay + self.assertFalse(channel.writable(t=lambda: later)) + self.assertEqual(channel.last_writable_check, _NOW) + + def test_writable_with_delay_is_True_if_elapsed_gt_delay(self): + channel = self._makeOne() + channel.delay = 2 + channel.last_writable_check = _NOW + later = _NOW + channel.delay + 0.1 + self.assertTrue(channel.writable(t=lambda: later)) + self.assertEqual(channel.last_writable_check, later) + + def test_writable_with_delay_is_True_if_system_time_goes_backwards(self): + channel = self._makeOne() + channel.delay = 2 + channel.last_writable_check = _NOW + 3600 # last check was in the future + later = _NOW + self.assertTrue(channel.writable(t=lambda: later)) + self.assertEqual(channel.last_writable_check, later) + +_NOW = 1470085990 + class EncryptedDictionaryAuthorizedTests(unittest.TestCase): def _getTargetClass(self): from supervisor.http import encrypted_dictionary_authorizer