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 @@ class BaseEventLoop(events.AbstractEventLoop): 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 @@ class BaseEventLoop(events.AbstractEventLoop): 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 @@ class BaseEventLoop(events.AbstractEventLoop): 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 @@ class BaseEventLoop(events.AbstractEventLoop): 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 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): 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 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): '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 @@ class BaseEventTests(test_utils.TestCase): 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 BaseSelectorEventLoop 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 @@ class SelectorDatagramTransportTests(test_utils.TestCase): '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.