Skip to content

Commit e796b2f

Browse files
authored
bpo-27456: Ensure TCP_NODELAY is set on linux (#4231)
1 parent 4ac5150 commit e796b2f

File tree

6 files changed

+53
-31
lines changed

6 files changed

+53
-31
lines changed

Lib/asyncio/base_events.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,24 @@ def _set_reuseport(sock):
8282
'SO_REUSEPORT defined but not implemented.')
8383

8484

85-
def _is_stream_socket(sock):
86-
# Linux's socket.type is a bitmask that can include extra info
87-
# about socket, therefore we can't do simple
88-
# `sock_type == socket.SOCK_STREAM`.
89-
return (sock.type & socket.SOCK_STREAM) == socket.SOCK_STREAM
85+
def _is_stream_socket(sock_type):
86+
if hasattr(socket, 'SOCK_NONBLOCK'):
87+
# Linux's socket.type is a bitmask that can include extra info
88+
# about socket (like SOCK_NONBLOCK bit), therefore we can't do simple
89+
# `sock_type == socket.SOCK_STREAM`, see
90+
# https://github.com/torvalds/linux/blob/v4.13/include/linux/net.h#L77
91+
# for more details.
92+
return (sock_type & 0xF) == socket.SOCK_STREAM
93+
else:
94+
return sock_type == socket.SOCK_STREAM
9095

9196

92-
def _is_dgram_socket(sock):
93-
# Linux's socket.type is a bitmask that can include extra info
94-
# about socket, therefore we can't do simple
95-
# `sock_type == socket.SOCK_DGRAM`.
96-
return (sock.type & socket.SOCK_DGRAM) == socket.SOCK_DGRAM
97+
def _is_dgram_socket(sock_type):
98+
if hasattr(socket, 'SOCK_NONBLOCK'):
99+
# See the comment in `_is_stream_socket`.
100+
return (sock_type & 0xF) == socket.SOCK_DGRAM
101+
else:
102+
return sock_type == socket.SOCK_DGRAM
97103

98104

99105
def _ipaddr_info(host, port, family, type, proto):
@@ -106,14 +112,9 @@ def _ipaddr_info(host, port, family, type, proto):
106112
host is None:
107113
return None
108114

109-
if type == socket.SOCK_STREAM:
110-
# Linux only:
111-
# getaddrinfo() can raise when socket.type is a bit mask.
112-
# So if socket.type is a bit mask of SOCK_STREAM, and say
113-
# SOCK_NONBLOCK, we simply return None, which will trigger
114-
# a call to getaddrinfo() letting it process this request.
115+
if _is_stream_socket(type):
115116
proto = socket.IPPROTO_TCP
116-
elif type == socket.SOCK_DGRAM:
117+
elif _is_dgram_socket(type):
117118
proto = socket.IPPROTO_UDP
118119
else:
119120
return None
@@ -758,7 +759,7 @@ async def create_connection(self, protocol_factory, host=None, port=None,
758759
if sock is None:
759760
raise ValueError(
760761
'host and port was not specified and no sock specified')
761-
if not _is_stream_socket(sock):
762+
if not _is_stream_socket(sock.type):
762763
# We allow AF_INET, AF_INET6, AF_UNIX as long as they
763764
# are SOCK_STREAM.
764765
# We support passing AF_UNIX sockets even though we have
@@ -808,7 +809,7 @@ async def create_datagram_endpoint(self, protocol_factory,
808809
allow_broadcast=None, sock=None):
809810
"""Create datagram connection."""
810811
if sock is not None:
811-
if not _is_dgram_socket(sock):
812+
if not _is_dgram_socket(sock.type):
812813
raise ValueError(
813814
f'A UDP Socket was expected, got {sock!r}')
814815
if (local_addr or remote_addr or
@@ -1036,7 +1037,7 @@ async def create_server(self, protocol_factory, host=None, port=None,
10361037
else:
10371038
if sock is None:
10381039
raise ValueError('Neither host/port nor sock were specified')
1039-
if not _is_stream_socket(sock):
1040+
if not _is_stream_socket(sock.type):
10401041
raise ValueError(f'A Stream Socket was expected, got {sock!r}')
10411042
sockets = [sock]
10421043

@@ -1059,7 +1060,7 @@ async def connect_accepted_socket(self, protocol_factory, sock,
10591060
This method is a coroutine. When completed, the coroutine
10601061
returns a (transport, protocol) pair.
10611062
"""
1062-
if not _is_stream_socket(sock):
1063+
if not _is_stream_socket(sock.type):
10631064
raise ValueError(f'A Stream Socket was expected, got {sock!r}')
10641065

10651066
transport, protocol = await self._create_connection_transport(

Lib/asyncio/selector_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def _test_selector_event(selector, fd, event):
4141
if hasattr(socket, 'TCP_NODELAY'):
4242
def _set_nodelay(sock):
4343
if (sock.family in {socket.AF_INET, socket.AF_INET6} and
44-
sock.type == socket.SOCK_STREAM and
44+
base_events._is_stream_socket(sock.type) and
4545
sock.proto == socket.IPPROTO_TCP):
4646
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
4747
else:

Lib/asyncio/unix_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ async def create_unix_connection(self, protocol_factory, path=None, *,
222222
if sock is None:
223223
raise ValueError('no path and sock were specified')
224224
if (sock.family != socket.AF_UNIX or
225-
not base_events._is_stream_socket(sock)):
225+
not base_events._is_stream_socket(sock.type)):
226226
raise ValueError(
227227
f'A UNIX Domain Stream Socket was expected, got {sock!r}')
228228
sock.setblocking(False)
@@ -276,7 +276,7 @@ async def create_unix_server(self, protocol_factory, path=None, *,
276276
'path was not specified, and no sock specified')
277277

278278
if (sock.family != socket.AF_UNIX or
279-
not base_events._is_stream_socket(sock)):
279+
not base_events._is_stream_socket(sock.type)):
280280
raise ValueError(
281281
f'A UNIX Domain Stream Socket was expected, got {sock!r}')
282282

Lib/test/test_asyncio/test_base_events.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,6 @@ def test_ipaddr_info(self):
107107
self.assertIsNone(
108108
base_events._ipaddr_info('::3%lo0', 1, INET6, STREAM, TCP))
109109

110-
if hasattr(socket, 'SOCK_NONBLOCK'):
111-
self.assertEqual(
112-
None,
113-
base_events._ipaddr_info(
114-
'1.2.3.4', 1, INET, STREAM | socket.SOCK_NONBLOCK, TCP))
115-
116-
117110
def test_port_parameter_types(self):
118111
# Test obscure kinds of arguments for "port".
119112
INET = socket.AF_INET

Lib/test/test_asyncio/test_selector_events.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from asyncio.selector_events import _SelectorTransport
1616
from asyncio.selector_events import _SelectorSocketTransport
1717
from asyncio.selector_events import _SelectorDatagramTransport
18+
from asyncio.selector_events import _set_nodelay
1819
from test.test_asyncio import utils as test_utils
1920

2021

@@ -1493,5 +1494,31 @@ def test_fatal_error_connected(self, m_exc):
14931494
'Fatal error on transport\nprotocol:.*\ntransport:.*'),
14941495
exc_info=(ConnectionRefusedError, MOCK_ANY, MOCK_ANY))
14951496

1497+
1498+
class TestSelectorUtils(test_utils.TestCase):
1499+
def check_set_nodelay(self, sock):
1500+
opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
1501+
self.assertFalse(opt)
1502+
1503+
_set_nodelay(sock)
1504+
1505+
opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
1506+
self.assertTrue(opt)
1507+
1508+
@unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'),
1509+
'need socket.TCP_NODELAY')
1510+
def test_set_nodelay(self):
1511+
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM,
1512+
proto=socket.IPPROTO_TCP)
1513+
with sock:
1514+
self.check_set_nodelay(sock)
1515+
1516+
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM,
1517+
proto=socket.IPPROTO_TCP)
1518+
with sock:
1519+
sock.setblocking(False)
1520+
self.check_set_nodelay(sock)
1521+
1522+
14961523
if __name__ == '__main__':
14971524
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure TCP_NODELAY is set on Linux. Tests by Victor Stinner.

0 commit comments

Comments
 (0)