]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Avoid spurious warnings at shutdown
authorAntoine Pitrou <antoine@python.org>
Tue, 15 Nov 2016 09:38:18 +0000 (10:38 +0100)
committerAntoine Pitrou <antoine@python.org>
Tue, 15 Nov 2016 09:38:18 +0000 (10:38 +0100)
When there are still active coroutines at interpreter shutdown, you can
see sometimes warnings such as:

Exception ignored in: <bound method Future.__del__ of <tornado.concurrent.Future object at 0x7f56a8b142e8>>
Traceback (most recent call last):
  File "/home/antoine/tornado/tornado/concurrent.py", line 338, in __del__
TypeError: 'NoneType' object is not callable

These are distracting and don't bear any useful information, so silence them.

tornado/concurrent.py
tornado/test/concurrent_test.py
tornado/test/util_test.py
tornado/util.py

index 05205f7374c7eb826b42245a3f03679e288c51d9..ec68dc4fe337388bf50c50dcee12e0b26783ea95 100644 (file)
@@ -31,7 +31,7 @@ import sys
 
 from tornado.log import app_log
 from tornado.stack_context import ExceptionStackContext, wrap
-from tornado.util import raise_exc_info, ArgReplacer
+from tornado.util import raise_exc_info, ArgReplacer, is_finalizing
 
 try:
     from concurrent import futures
@@ -123,8 +123,8 @@ class _TracebackLogger(object):
         self.exc_info = None
         self.formatted_tb = None
 
-    def __del__(self):
-        if self.formatted_tb:
+    def __del__(self, is_finalizing=is_finalizing):
+        if not is_finalizing() and self.formatted_tb:
             app_log.error('Future exception was never retrieved: %s',
                           ''.join(self.formatted_tb).rstrip())
 
@@ -329,8 +329,8 @@ class Future(object):
     # cycle are never destroyed. It's no longer the case on Python 3.4 thanks to
     # the PEP 442.
     if _GC_CYCLE_FINALIZERS:
-        def __del__(self):
-            if not self._log_traceback:
+        def __del__(self, is_finalizing=is_finalizing):
+            if is_finalizing() or not self._log_traceback:
                 # set_exception() was not called, or result() or exception()
                 # has consumed the exception
                 return
index 8ce095ec1b3118e2d49a99b168c844d9399d139e..92a4a62d6c58eaefe7bb656e8eb6b7b972a993bb 100644 (file)
@@ -25,9 +25,10 @@ from tornado.concurrent import Future, return_future, ReturnValueIgnoredError, r
 from tornado.escape import utf8, to_unicode
 from tornado import gen
 from tornado.iostream import IOStream
+from tornado.log import app_log
 from tornado import stack_context
 from tornado.tcpserver import TCPServer
-from tornado.testing import AsyncTestCase, LogTrapTestCase, bind_unused_port, gen_test
+from tornado.testing import AsyncTestCase, ExpectLog, LogTrapTestCase, bind_unused_port, gen_test
 from tornado.test.util import unittest
 
 
@@ -171,6 +172,23 @@ class ReturnFutureTest(AsyncTestCase):
             tb = traceback.extract_tb(sys.exc_info()[2])
             self.assertIn(self.expected_frame, tb)
 
+    @gen_test
+    def test_uncaught_exception_log(self):
+        @gen.coroutine
+        def f():
+            yield gen.moment
+            1/0
+
+        g = f()
+
+        with ExpectLog(app_log,
+                       "(?s)Future.* exception was never retrieved:"
+                       ".*ZeroDivisionError"):
+            yield gen.moment
+            yield gen.moment
+            del g
+
+
 # The following series of classes demonstrate and test various styles
 # of use, with and without generators and futures.
 
index 48b16f89e697276e3d445984050821e7a98b389f..cbb89322edddc7622d28b394e3c4ec90fe1fb4ed 100644 (file)
@@ -6,7 +6,7 @@ import datetime
 
 import tornado.escape
 from tornado.escape import utf8
-from tornado.util import raise_exc_info, Configurable, exec_in, ArgReplacer, timedelta_to_seconds, import_object, re_unescape, PY3
+from tornado.util import raise_exc_info, Configurable, exec_in, ArgReplacer, timedelta_to_seconds, import_object, re_unescape, is_finalizing, PY3
 from tornado.test.util import unittest
 
 if PY3:
@@ -220,3 +220,8 @@ class ReUnescapeTest(unittest.TestCase):
             re_unescape('\\b')
         with self.assertRaises(ValueError):
             re_unescape('\\Z')
+
+
+class IsFinalizingTest(unittest.TestCase):
+    def test_basic(self):
+        self.assertFalse(is_finalizing())
index 28e74e7dc0e3be3563b5868f34bcdc294be45b50..174c35df681ff1183bbb27c4f1c0de433eb6c7a3 100644 (file)
@@ -13,6 +13,7 @@ and `.Resolver`.
 from __future__ import absolute_import, division, print_function, with_statement
 
 import array
+import atexit
 import os
 import re
 import sys
@@ -66,6 +67,22 @@ else:
         _BaseString = Union[bytes, unicode_type]
 
 
+try:
+    from sys import is_finalizing
+except ImportError:
+    # Emulate it
+    def _get_emulated_is_finalizing():
+        L = []
+        atexit.register(lambda: L.append(None))
+
+        def is_finalizing():
+            return bool(L)
+
+        return is_finalizing
+
+    is_finalizing = _get_emulated_is_finalizing()
+
+
 class ObjectDict(_ObjectDictBase):
     """Makes a dictionary behave like an object, with attribute-style access.
     """