diff --git a/tornado/test/import_test.py b/tornado/test/import_test.py index 0e981482..ecc569eb 100644 --- a/tornado/test/import_test.py +++ b/tornado/test/import_test.py @@ -1,50 +1,68 @@ # flake8: noqa from __future__ import absolute_import, division, print_function + +import subprocess +import sys + from tornado.test.util import unittest +_import_everything = b""" +# The event loop is not fork-safe, and it's easy to initialize an asyncio.Future +# at startup, which in turn creates the default event loop and prevents forking. +# Explicitly disallow the default event loop so that an error will be raised +# if something tries to touch it. +try: + import asyncio +except ImportError: + pass +else: + asyncio.set_event_loop(None) + +import tornado.auth +import tornado.autoreload +import tornado.concurrent +import tornado.escape +import tornado.gen +import tornado.http1connection +import tornado.httpclient +import tornado.httpserver +import tornado.httputil +import tornado.ioloop +import tornado.iostream +import tornado.locale +import tornado.log +import tornado.netutil +import tornado.options +import tornado.process +import tornado.simple_httpclient +import tornado.stack_context +import tornado.tcpserver +import tornado.tcpclient +import tornado.template +import tornado.testing +import tornado.util +import tornado.web +import tornado.websocket +import tornado.wsgi + +try: + import pycurl +except ImportError: + pass +else: + import tornado.curl_httpclient +""" + class ImportTest(unittest.TestCase): def test_import_everything(self): - # Some of our modules are not otherwise tested. Import them - # all (unless they have external dependencies) here to at - # least ensure that there are no syntax errors. - import tornado.auth - import tornado.autoreload - import tornado.concurrent - import tornado.escape - import tornado.gen - import tornado.http1connection - import tornado.httpclient - import tornado.httpserver - import tornado.httputil - import tornado.ioloop - import tornado.iostream - import tornado.locale - import tornado.log - import tornado.netutil - import tornado.options - import tornado.process - import tornado.simple_httpclient - import tornado.stack_context - import tornado.tcpserver - import tornado.tcpclient - import tornado.template - import tornado.testing - import tornado.util - import tornado.web - import tornado.websocket - import tornado.wsgi - - # for modules with dependencies, if those dependencies can be loaded, - # load them too. - - def test_import_pycurl(self): - try: - import pycurl # type: ignore - except ImportError: - pass - else: - import tornado.curl_httpclient + # Test that all Tornado modules can be imported without side effects, + # specifically without initializing the default asyncio event loop. + # Since we can't tell which modules may have already beein imported + # in our process, do it in a subprocess for a clean slate. + proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE) + proc.communicate(_import_everything) + self.assertEqual(proc.returncode, 0) def test_import_aliases(self): # Ensure we don't delete formerly-documented aliases accidentally. diff --git a/tornado/wsgi.py b/tornado/wsgi.py index beda0b9a..e1230da0 100644 --- a/tornado/wsgi.py +++ b/tornado/wsgi.py @@ -85,8 +85,10 @@ class WSGIApplication(web.Application): # WSGI has no facilities for flow control, so just return an already-done # Future when the interface requires it. -_dummy_future = Future() -_dummy_future.set_result(None) +def _dummy_future(): + f = Future() + f.set_result(None) + return f class _WSGIConnection(httputil.HTTPConnection): @@ -118,7 +120,7 @@ class _WSGIConnection(httputil.HTTPConnection): self.write(chunk, callback) elif callback is not None: callback() - return _dummy_future + return _dummy_future() def write(self, chunk, callback=None): if self._expected_content_remaining is not None: @@ -130,7 +132,7 @@ class _WSGIConnection(httputil.HTTPConnection): self._write_buffer.append(chunk) if callback is not None: callback() - return _dummy_future + return _dummy_future() def finish(self): if (self._expected_content_remaining is not None and