]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Prevent a deadlock if ThreadedResolver resolves a unicode hostname at module-import... 964/head
authorA. Jesse Jiryu Davis <jesse@10gen.com>
Mon, 16 Dec 2013 23:56:05 +0000 (18:56 -0500)
committerA. Jesse Jiryu Davis <jesse@10gen.com>
Mon, 23 Dec 2013 03:00:21 +0000 (21:00 -0600)
tornado/netutil.py
tornado/test/netutil_test.py
tornado/test/resolve_test.py [new file with mode: 0644]

index 21db47557940726effc1bd1f89f91bf658c2d2a2..8ebe604d2f98e526b84baf5b5917ec5c9e5a310f 100644 (file)
@@ -27,7 +27,7 @@ import stat
 from tornado.concurrent import dummy_executor, run_on_executor
 from tornado.ioloop import IOLoop
 from tornado.platform.auto import set_close_exec
-from tornado.util import Configurable
+from tornado.util import u, Configurable
 
 if hasattr(ssl, 'match_hostname') and hasattr(ssl, 'CertificateError'):  # python 3.2+
     ssl_match_hostname = ssl.match_hostname
@@ -37,6 +37,14 @@ else:
     ssl_match_hostname = backports.ssl_match_hostname.match_hostname
     SSLCertificateError = backports.ssl_match_hostname.CertificateError
 
+# ThreadedResolver runs getaddrinfo on a thread. If the hostname is unicode,
+# getaddrinfo attempts to import encodings.idna. If this is done at
+# module-import time, the import lock is already held by the main thread,
+# leading to deadlock. Avoid it by caching the idna encoder on the main
+# thread now.
+u('foo').encode('idna')
+
+
 def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128, flags=None):
     """Creates listening sockets bound to the given port and address.
 
index c47e58fa34171af4913d25a3eb10c9fc2c769886..2501eab503721fd619140be3d786492c428f3d7c 100644 (file)
@@ -1,6 +1,9 @@
 from __future__ import absolute_import, division, print_function, with_statement
 
 import socket
+from subprocess import Popen
+import sys
+import time
 
 from tornado.netutil import BlockingResolver, ThreadedResolver, is_valid_ip
 from tornado.testing import AsyncTestCase, gen_test
@@ -57,6 +60,28 @@ class ThreadedResolverTest(AsyncTestCase, _ResolverTestMixin):
         super(ThreadedResolverTest, self).tearDown()
 
 
+@unittest.skipIf(futures is None, "futures module not present")
+class ThreadedResolverImportTest(unittest.TestCase):
+    def test_import(self):
+        # Test for a deadlock when importing a module that runs the
+        # ThreadedResolver at import-time. See resolve_test.py for
+        # full explanation.
+        command = [
+            sys.executable,
+            '-c',
+            'import tornado.test.resolve_test']
+
+        start = time.time()
+        popen = Popen(command)
+        while time.time() - start < 20:
+            return_code = popen.poll()
+            if return_code is not None:
+                self.assertEqual(0, return_code)
+                return  # Success.
+
+        self.fail("import timed out")
+
+
 @unittest.skipIf(pycares is None, "pycares module not present")
 class CaresResolverTest(AsyncTestCase, _ResolverTestMixin):
     def setUp(self):
diff --git a/tornado/test/resolve_test.py b/tornado/test/resolve_test.py
new file mode 100644 (file)
index 0000000..5df8061
--- /dev/null
@@ -0,0 +1,11 @@
+from tornado.ioloop import IOLoop
+from tornado.netutil import ThreadedResolver
+from tornado.util import u
+
+# When this module is imported, it runs getaddrinfo on a thread. Since
+# the hostname is unicode, getaddrinfo attempts to import encodings.idna
+# but blocks on the import lock. Verify that ThreadedResolver avoids
+# this deadlock.
+
+resolver = ThreadedResolver()
+IOLoop.current().run_sync(lambda: resolver.resolve(u('tornadoweb.org'), 80))