Repository: tornadoweb/tornado

Issue:
autoreload: Request will be always loading after auto reload
This commit fa809ed12348fea956444720ffc653516e4651e0 removes the code that closes the IOLoop on reload, that will not close the keep alive connections too. Once the code changed and auto reload done,  I refresh the browser it will be always loading.

I close the IOLoop on reload manually and it works well.

- OS: macOS 10.12.4
- Python 2.7.13
- Tornado 4.5.1


Discussion:
All of the connections created by Tornado should have the CLOEXEC (close-on-exec) bit set, and `autoreload` uses `exec`. So all connections are supposed to be closed automatically even without closing the IOLoop. 

Do you have a simple example that reproduces this problem? Are the "keep-alive" connections coming from a server or client? Are you using the curl HTTP client? Are any non-Tornado packages using the network?
@bdarnell Thanks for you reply, I only use the Tornado for web develop and use Chrome(58.0.3029.110 (64-bit)) for debug.
I can't reproduce this problem. When I use autoreload in a simple hello world application (the one in `demos/helloworld/helloworld.py` with `debug=True` added to the `Application` constructor), all the keep-alive connections are closed as expected. You're going to need to do some more debugging in your own environment or provide a reproducible test case to figure out what's going on. 
@bdarnell It seems can only reproduce this on macOS, everyone in my teams who use macOS will got this problem. I can't reproduce this problem on Linux. And I also find this http://bugs.python.org/issue26343.
UPDATE: Python 3.6.1 cant' reproduce this problem.
OK, I can confirm that this is an issue on Python 2.7. Python 3.3 changed things so that new connections have the `CLOEXEC` flag set by default. (http://bugs.python.org/issue26343 is about the `O_CLOEXEC` flag, but we don't use that - we use `FD_CLOEXEC` instead, which is present)

When I wrote fa809ed1234 I believed we were reliably setting `CLOEXEC` on every connection. But now I can't find where we do that, and even looking at git logs I can't find where it might have changed. So it looks like this was a mistake and we should add calls to `set_close_exec` on all connections (and this should probably go into a 4.5.2 patch release). 

Resolving patch:
diff --git a/tornado/netutil.py b/tornado/netutil.py
--- a/tornado/netutil.py
+++ b/tornado/netutil.py
@@ -270,6 +270,7 @@ def accept_handler(fd, events):
                 if errno_from_exception(e) == errno.ECONNABORTED:
                     continue
                 raise
+            set_close_exec(connection.fileno())
             callback(connection, address)
     IOLoop.current().add_handler(sock, accept_handler, IOLoop.READ)
 
diff --git a/tornado/platform/common.py b/tornado/platform/common.py
--- a/tornado/platform/common.py
+++ b/tornado/platform/common.py
@@ -32,10 +32,12 @@ class Waker(interface.Waker):
     and Jython.
     """
     def __init__(self):
+        from .auto import set_close_exec
         # Based on Zope select_trigger.py:
         # https://github.com/zopefoundation/Zope/blob/master/src/ZServer/medusa/thread/select_trigger.py
 
         self.writer = socket.socket()
+        set_close_exec(self.writer.fileno())
         # Disable buffering -- pulling the trigger sends 1 byte,
         # and we want that sent immediately, to wake up ASAP.
         self.writer.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
@@ -54,6 +56,7 @@ def __init__(self):
             # http://mail.zope.org/pipermail/zope/2005-July/160433.html
             # for hideous details.
             a = socket.socket()
+            set_close_exec(a.fileno())
             a.bind(("127.0.0.1", 0))
             a.listen(1)
             connect_address = a.getsockname()  # assigned (host, port) pair
@@ -78,6 +81,7 @@ def __init__(self):
                 a.close()
 
         self.reader, addr = a.accept()
+        set_close_exec(self.reader.fileno())
         self.reader.setblocking(0)
         self.writer.setblocking(0)
         a.close()
diff --git a/tornado/tcpclient.py b/tornado/tcpclient.py
--- a/tornado/tcpclient.py
+++ b/tornado/tcpclient.py
@@ -26,6 +26,7 @@
 from tornado.iostream import IOStream
 from tornado import gen
 from tornado.netutil import Resolver
+from tornado.platform.auto import set_close_exec
 
 _INITIAL_CONNECT_TIMEOUT = 0.3
 
@@ -201,6 +202,7 @@ def _create_stream(self, max_buffer_size, af, addr, source_ip=None,
             # - 127.0.0.1 for IPv4
             # - ::1 for IPv6
         socket_obj = socket.socket(af)
+        set_close_exec(socket_obj.fileno())
         if source_port_bind or source_ip_bind:
             # If the user requires binding also to a specific IP/port.
             try:


Test patch: