Просмотр исходного кода

Fix crash if system time is far into the future. Closes #213

Mike Naberezny 9 лет назад
Родитель
Сommit
98aac98f30

+ 9 - 0
CHANGES.txt

@@ -31,6 +31,15 @@
 - Files included via the ``[include]`` section are now logged at the ``INFO``
   level instead of ``WARN``.  Patch by Daniel Hahler.
 
+- Fixed a server exception ``OverflowError: int exceeds XML-RPC limits`` that
+  made ``supervisorctl status`` unusable if the system time was far into the
+  future.  The XML-RPC API returns timestamps as XML-RPC integers, but
+  timestamps will exceed the maximum value of an XML-RPC integer in January
+  2038 ("Year 2038 Problem").  For now, timestamps exceeding the maximum
+  integer will be capped at the maximum to avoid the exception and retain
+  compatibility with existing API clients.  In a future version of the API,
+  the return type for timestamps will be changed.
+
 3.2.0 (2015-11-30)
 ------------------
 

+ 15 - 5
supervisor/rpcinterface.py

@@ -23,8 +23,11 @@ from supervisor.events import notify
 from supervisor.events import RemoteCommunicationEvent
 
 from supervisor.http import NOT_DONE_YET
-from supervisor.xmlrpc import Faults
-from supervisor.xmlrpc import RPCError
+from supervisor.xmlrpc import (
+    capped_int,
+    Faults,
+    RPCError,
+    )
 
 from supervisor.states import SupervisorStates
 from supervisor.states import getSupervisorStateDescription
@@ -623,9 +626,12 @@ class SupervisorNamespaceRPCInterface:
         if process is None:
             raise RPCError(Faults.BAD_NAME, name)
 
-        start = int(process.laststart)
-        stop = int(process.laststop)
-        now = int(time.time())
+        # TODO timestamps are returned as xml-rpc integers for b/c but will
+        # saturate the xml-rpc integer type in jan 2038 ("year 2038 problem").
+        # future api versions should return timestamps as a different type.
+        start = capped_int(process.laststart)
+        stop = capped_int(process.laststop)
+        now = capped_int(self._now())
 
         state = process.get_state()
         spawnerr = process.spawnerr or ''
@@ -653,6 +659,10 @@ class SupervisorNamespaceRPCInterface:
         info['description'] = description
         return info
 
+    def _now(self): # pragma: no cover
+        # this is here to service stubbing in unit tests
+        return time.time()
+
     def getAllProcessInfo(self):
         """ Get info about all processes
 

+ 19 - 0
supervisor/tests/test_rpcinterfaces.py

@@ -1312,6 +1312,25 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self._assertRPCError(xmlrpc.Faults.BAD_NAME,
                              interface.getProcessInfo, 'foo:')
 
+    def test_getProcessInfo_caps_timestamps_exceeding_xmlrpc_maxint(self):
+        from supervisor.compat import xmlrpclib
+        options = DummyOptions()
+        config = DummyPConfig(options, 'foo', '/bin/foo',
+                              stdout_logfile=None)
+        process = DummyProcess(config)
+        process.laststart = float(xmlrpclib.MAXINT + 1)
+        process.laststop = float(xmlrpclib.MAXINT + 1)
+        pgroup_config = DummyPGroupConfig(options, name='foo')
+        pgroup = DummyProcessGroup(pgroup_config)
+        pgroup.processes = {'foo':process}
+        supervisord = DummySupervisor(process_groups={'foo':pgroup})
+        interface = self._makeOne(supervisord)
+        interface._now = lambda: float(xmlrpclib.MAXINT + 1)
+        data = interface.getProcessInfo('foo')
+        self.assertEqual(data['start'], xmlrpclib.MAXINT)
+        self.assertEqual(data['stop'], xmlrpclib.MAXINT)
+        self.assertEqual(data['now'], xmlrpclib.MAXINT)
+
     def test_getAllProcessInfo(self):
         from supervisor.process import ProcessStates
         options = DummyOptions()

+ 17 - 0
supervisor/tests/test_xmlrpc.py

@@ -753,6 +753,23 @@ class Test_gettags(unittest.TestCase):
             [(0, None, None, None, ''), (0, 'foo', 'array', 'name', 'text')]
             )
 
+class Test_capped_int(unittest.TestCase):
+    def _callFUT(self, comment):
+        from supervisor.xmlrpc import capped_int
+        return capped_int(comment)
+
+    def test_converts_value_to_integer(self):
+        self.assertEqual(self._callFUT('42'), 42)
+
+    def test_caps_value_below_minint(self):
+        from supervisor.compat import xmlrpclib
+        self.assertEqual(self._callFUT(xmlrpclib.MININT - 1), xmlrpclib.MININT)
+
+    def test_caps_value_above_maxint(self):
+        from supervisor.compat import xmlrpclib
+        self.assertEqual(self._callFUT(xmlrpclib.MAXINT + 1), xmlrpclib.MAXINT)
+
+
 class DummyResponse:
     def __init__(self, status=200, body='', reason='reason'):
         self.status = status

+ 8 - 0
supervisor/xmlrpc.py

@@ -314,6 +314,14 @@ class RootRPCInterface:
         for name, rpcinterface in subinterfaces:
             setattr(self, name, rpcinterface)
 
+def capped_int(value):
+    i = int(value)
+    if i < xmlrpclib.MININT:
+        i = xmlrpclib.MININT
+    elif i > xmlrpclib.MAXINT:
+        i = xmlrpclib.MAXINT
+    return i
+
 def make_datetime(text):
     return datetime.datetime(
         *time.strptime(text, "%Y%m%dT%H:%M:%S")[:6]