[Python-checkins] bpo-27456: Ensure TCP_NODELAY is set on linux (#4231)

Yury Selivanov webhook-mailer at python.org
Fri Dec 15 19:32:27 EST 2017


https://github.com/python/cpython/commit/e796b2fe26f220107ac50667de6cc86c82b465e3
commit: e796b2fe26f220107ac50667de6cc86c82b465e3
branch: master
author: Yury Selivanov <yury at magic.io>
committer: GitHub <noreply at github.com>
date: 2017-12-15T19:32:25-05:00
summary:

bpo-27456: Ensure TCP_NODELAY is set on linux (#4231)

files:
A Misc/NEWS.d/next/Library/2017-11-02-11-57-41.bpo-27456.snzyTC.rst
M Lib/asyncio/base_events.py
M Lib/asyncio/selector_events.py
M Lib/asyncio/unix_events.py
M Lib/test/test_asyncio/test_base_events.py
M Lib/test/test_asyncio/test_selector_events.py

diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 80d2b693f1d..398497de09f 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -82,18 +82,24 @@ def _set_reuseport(sock):
                              'SO_REUSEPORT defined but not implemented.')
 
 
-def _is_stream_socket(sock):
-    # Linux's socket.type is a bitmask that can include extra info
-    # about socket, therefore we can't do simple
-    # `sock_type == socket.SOCK_STREAM`.
-    return (sock.type & socket.SOCK_STREAM) == socket.SOCK_STREAM
+def _is_stream_socket(sock_type):
+    if hasattr(socket, 'SOCK_NONBLOCK'):
+        # Linux's socket.type is a bitmask that can include extra info
+        # about socket (like SOCK_NONBLOCK bit), therefore we can't do simple
+        # `sock_type == socket.SOCK_STREAM`, see
+        # https://github.com/torvalds/linux/blob/v4.13/include/linux/net.h#L77
+        # for more details.
+        return (sock_type & 0xF) == socket.SOCK_STREAM
+    else:
+        return sock_type == socket.SOCK_STREAM
 
 
-def _is_dgram_socket(sock):
-    # Linux's socket.type is a bitmask that can include extra info
-    # about socket, therefore we can't do simple
-    # `sock_type == socket.SOCK_DGRAM`.
-    return (sock.type & socket.SOCK_DGRAM) == socket.SOCK_DGRAM
+def _is_dgram_socket(sock_type):
+    if hasattr(socket, 'SOCK_NONBLOCK'):
+        # See the comment in `_is_stream_socket`.
+        return (sock_type & 0xF) == socket.SOCK_DGRAM
+    else:
+        return sock_type == socket.SOCK_DGRAM
 
 
 def _ipaddr_info(host, port, family, type, proto):
@@ -106,14 +112,9 @@ def _ipaddr_info(host, port, family, type, proto):
             host is None:
         return None
 
-    if type == socket.SOCK_STREAM:
-        # Linux only:
-        #    getaddrinfo() can raise when socket.type is a bit mask.
-        #    So if socket.type is a bit mask of SOCK_STREAM, and say
-        #    SOCK_NONBLOCK, we simply return None, which will trigger
-        #    a call to getaddrinfo() letting it process this request.
+    if _is_stream_socket(type):
         proto = socket.IPPROTO_TCP
-    elif type == socket.SOCK_DGRAM:
+    elif _is_dgram_socket(type):
         proto = socket.IPPROTO_UDP
     else:
         return None
@@ -758,7 +759,7 @@ def _getaddrinfo_debug(self, host, port, family, type, proto, flags):
             if sock is None:
                 raise ValueError(
                     'host and port was not specified and no sock specified')
-            if not _is_stream_socket(sock):
+            if not _is_stream_socket(sock.type):
                 # We allow AF_INET, AF_INET6, AF_UNIX as long as they
                 # are SOCK_STREAM.
                 # We support passing AF_UNIX sockets even though we have
@@ -808,7 +809,7 @@ def _getaddrinfo_debug(self, host, port, family, type, proto, flags):
                                        allow_broadcast=None, sock=None):
         """Create datagram connection."""
         if sock is not None:
-            if not _is_dgram_socket(sock):
+            if not _is_dgram_socket(sock.type):
                 raise ValueError(
                     f'A UDP Socket was expected, got {sock!r}')
             if (local_addr or remote_addr or
@@ -1036,7 +1037,7 @@ def _getaddrinfo_debug(self, host, port, family, type, proto, flags):
         else:
             if sock is None:
                 raise ValueError('Neither host/port nor sock were specified')
-            if not _is_stream_socket(sock):
+            if not _is_stream_socket(sock.type):
                 raise ValueError(f'A Stream Socket was expected, got {sock!r}')
             sockets = [sock]
 
@@ -1059,7 +1060,7 @@ def _getaddrinfo_debug(self, host, port, family, type, proto, flags):
         This method is a coroutine.  When completed, the coroutine
         returns a (transport, protocol) pair.
         """
-        if not _is_stream_socket(sock):
+        if not _is_stream_socket(sock.type):
             raise ValueError(f'A Stream Socket was expected, got {sock!r}')
 
         transport, protocol = await self._create_connection_transport(
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index 78ebf3e5fca..cb33cd34b87 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -41,7 +41,7 @@ def _test_selector_event(selector, fd, event):
 if hasattr(socket, 'TCP_NODELAY'):
     def _set_nodelay(sock):
         if (sock.family in {socket.AF_INET, socket.AF_INET6} and
-                sock.type == socket.SOCK_STREAM and
+                base_events._is_stream_socket(sock.type) and
                 sock.proto == socket.IPPROTO_TCP):
             sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 else:
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index b9bdf8786f2..50d78c88545 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -222,7 +222,7 @@ def _child_watcher_callback(self, pid, returncode, transp):
             if sock is None:
                 raise ValueError('no path and sock were specified')
             if (sock.family != socket.AF_UNIX or
-                    not base_events._is_stream_socket(sock)):
+                    not base_events._is_stream_socket(sock.type)):
                 raise ValueError(
                     f'A UNIX Domain Stream Socket was expected, got {sock!r}')
             sock.setblocking(False)
@@ -276,7 +276,7 @@ def _child_watcher_callback(self, pid, returncode, transp):
                     'path was not specified, and no sock specified')
 
             if (sock.family != socket.AF_UNIX or
-                    not base_events._is_stream_socket(sock)):
+                    not base_events._is_stream_socket(sock.type)):
                 raise ValueError(
                     f'A UNIX Domain Stream Socket was expected, got {sock!r}')
 
diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py
index f8427cd5a86..1d45cf86425 100644
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -107,13 +107,6 @@ def test_ipaddr_info(self):
         self.assertIsNone(
             base_events._ipaddr_info('::3%lo0', 1, INET6, STREAM, TCP))
 
-        if hasattr(socket, 'SOCK_NONBLOCK'):
-            self.assertEqual(
-                None,
-                base_events._ipaddr_info(
-                    '1.2.3.4', 1, INET, STREAM | socket.SOCK_NONBLOCK, TCP))
-
-
     def test_port_parameter_types(self):
         # Test obscure kinds of arguments for "port".
         INET = socket.AF_INET
diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
index 04b0f97b2ab..b1ca3fcf0b1 100644
--- a/Lib/test/test_asyncio/test_selector_events.py
+++ b/Lib/test/test_asyncio/test_selector_events.py
@@ -15,6 +15,7 @@
 from asyncio.selector_events import _SelectorTransport
 from asyncio.selector_events import _SelectorSocketTransport
 from asyncio.selector_events import _SelectorDatagramTransport
+from asyncio.selector_events import _set_nodelay
 from test.test_asyncio import utils as test_utils
 
 
@@ -1493,5 +1494,31 @@ def test_fatal_error_connected(self, m_exc):
                 'Fatal error on transport\nprotocol:.*\ntransport:.*'),
             exc_info=(ConnectionRefusedError, MOCK_ANY, MOCK_ANY))
 
+
+class TestSelectorUtils(test_utils.TestCase):
+    def check_set_nodelay(self, sock):
+        opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
+        self.assertFalse(opt)
+
+        _set_nodelay(sock)
+
+        opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
+        self.assertTrue(opt)
+
+    @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'),
+                         'need socket.TCP_NODELAY')
+    def test_set_nodelay(self):
+        sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM,
+                             proto=socket.IPPROTO_TCP)
+        with sock:
+            self.check_set_nodelay(sock)
+
+        sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM,
+                             proto=socket.IPPROTO_TCP)
+        with sock:
+            sock.setblocking(False)
+            self.check_set_nodelay(sock)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2017-11-02-11-57-41.bpo-27456.snzyTC.rst b/Misc/NEWS.d/next/Library/2017-11-02-11-57-41.bpo-27456.snzyTC.rst
new file mode 100644
index 00000000000..fa7b5616bb3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-11-02-11-57-41.bpo-27456.snzyTC.rst
@@ -0,0 +1 @@
+Ensure TCP_NODELAY is set on Linux. Tests by Victor Stinner.



More information about the Python-checkins mailing list