From c85dfd2e11ded1edfa937c535d5eb6483e715597 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 28 May 2017 20:10:01 -0400 Subject: [PATCH] tcpclient,netutil: Set FD_CLOEXEC on all sockets created by Tornado PR #1984 was based on the mistaken belief that we were already doing this (and in python 3.4+, it's true, thanks to PEP 446). This fixes a regression introduced in Tornado 4.5 in which autoreload would leak file descriptors and leave client connections hanging. Fixes #2057 --- tornado/netutil.py | 1 + tornado/platform/common.py | 4 ++++ tornado/tcpclient.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/tornado/netutil.py b/tornado/netutil.py index c34c8c8b..5233b20f 100644 --- a/tornado/netutil.py +++ b/tornado/netutil.py @@ -273,6 +273,7 @@ def add_accept_handler(sock, callback, io_loop=None): if errno_from_exception(e) == errno.ECONNABORTED: continue raise + set_close_exec(connection.fileno()) callback(connection, address) io_loop.add_handler(sock, accept_handler, IOLoop.READ) diff --git a/tornado/platform/common.py b/tornado/platform/common.py index a73f8db7..b597748d 100644 --- 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 @@ class Waker(interface.Waker): # 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 @@ class Waker(interface.Waker): 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 index 33074bd5..bb5e9f34 100644 --- a/tornado/tcpclient.py +++ b/tornado/tcpclient.py @@ -26,6 +26,7 @@ from tornado.ioloop import IOLoop 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 @@ -202,6 +203,7 @@ class TCPClient(object): # - 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: