Skip to content

Commit 572636d

Browse files
authored
bpo-27456: Ensure TCP_NODELAY is set on linux (#4231) (#4898)
(cherry picked from commit e796b2f)
1 parent dab4cf2 commit 572636d

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
@@ -84,18 +84,24 @@ def _set_reuseport(sock):
8484
'SO_REUSEPORT defined but not implemented.')
8585

8686

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

9398

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

100106

101107
def _ipaddr_info(host, port, family, type, proto):
@@ -108,14 +114,9 @@ def _ipaddr_info(host, port, family, type, proto):
108114
host is None:
109115
return None
110116

111-
if type == socket.SOCK_STREAM:
112-
# Linux only:
113-
# getaddrinfo() can raise when socket.type is a bit mask.
114-
# So if socket.type is a bit mask of SOCK_STREAM, and say
115-
# SOCK_NONBLOCK, we simply return None, which will trigger
116-
# a call to getaddrinfo() letting it process this request.
117+
if _is_stream_socket(type):
117118
proto = socket.IPPROTO_TCP
118-
elif type == socket.SOCK_DGRAM:
119+
elif _is_dgram_socket(type):
119120
proto = socket.IPPROTO_UDP
120121
else:
121122
return None
@@ -789,7 +790,7 @@ def create_connection(self, protocol_factory, host=None, port=None, *,
789790
if sock is None:
790791
raise ValueError(
791792
'host and port was not specified and no sock specified')
792-
if not _is_stream_socket(sock):
793+
if not _is_stream_socket(sock.type):
793794
# We allow AF_INET, AF_INET6, AF_UNIX as long as they
794795
# are SOCK_STREAM.
795796
# We support passing AF_UNIX sockets even though we have
@@ -841,7 +842,7 @@ def create_datagram_endpoint(self, protocol_factory,
841842
allow_broadcast=None, sock=None):
842843
"""Create datagram connection."""
843844
if sock is not None:
844-
if not _is_dgram_socket(sock):
845+
if not _is_dgram_socket(sock.type):
845846
raise ValueError(
846847
'A UDP Socket was expected, got {!r}'.format(sock))
847848
if (local_addr or remote_addr or
@@ -1054,7 +1055,7 @@ def create_server(self, protocol_factory, host=None, port=None,
10541055
else:
10551056
if sock is None:
10561057
raise ValueError('Neither host/port nor sock were specified')
1057-
if not _is_stream_socket(sock):
1058+
if not _is_stream_socket(sock.type):
10581059
raise ValueError(
10591060
'A Stream Socket was expected, got {!r}'.format(sock))
10601061
sockets = [sock]
@@ -1078,7 +1079,7 @@ def connect_accepted_socket(self, protocol_factory, sock, *, ssl=None):
10781079
This method is a coroutine. When completed, the coroutine
10791080
returns a (transport, protocol) pair.
10801081
"""
1081-
if not _is_stream_socket(sock):
1082+
if not _is_stream_socket(sock.type):
10821083
raise ValueError(
10831084
'A Stream Socket was expected, got {!r}'.format(sock))
10841085

Lib/asyncio/selector_events.py

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

Lib/asyncio/unix_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def create_unix_connection(self, protocol_factory, path, *,
242242
if sock is None:
243243
raise ValueError('no path and sock were specified')
244244
if (sock.family != socket.AF_UNIX or
245-
not base_events._is_stream_socket(sock)):
245+
not base_events._is_stream_socket(sock.type)):
246246
raise ValueError(
247247
'A UNIX Domain Stream Socket was expected, got {!r}'
248248
.format(sock))
@@ -297,7 +297,7 @@ def create_unix_server(self, protocol_factory, path=None, *,
297297
'path was not specified, and no sock specified')
298298

299299
if (sock.family != socket.AF_UNIX or
300-
not base_events._is_stream_socket(sock)):
300+
not base_events._is_stream_socket(sock.type)):
301301
raise ValueError(
302302
'A UNIX Domain Stream Socket was expected, got {!r}'
303303
.format(sock))

Lib/test/test_asyncio/test_base_events.py

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

119-
if hasattr(socket, 'SOCK_NONBLOCK'):
120-
self.assertEqual(
121-
None,
122-
base_events._ipaddr_info(
123-
'1.2.3.4', 1, INET, STREAM | socket.SOCK_NONBLOCK, TCP))
124-
125-
126119
def test_port_parameter_types(self):
127120
# Test obscure kinds of arguments for "port".
128121
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
@@ -17,6 +17,7 @@
1717
from asyncio.selector_events import _SelectorSslTransport
1818
from asyncio.selector_events import _SelectorSocketTransport
1919
from asyncio.selector_events import _SelectorDatagramTransport
20+
from asyncio.selector_events import _set_nodelay
2021

2122

2223
MOCK_ANY = mock.ANY
@@ -1829,5 +1830,31 @@ def test_fatal_error_connected(self, m_exc):
18291830
'Fatal error on transport\nprotocol:.*\ntransport:.*'),
18301831
exc_info=(ConnectionRefusedError, MOCK_ANY, MOCK_ANY))
18311832

1833+
1834+
class TestSelectorUtils(test_utils.TestCase):
1835+
def check_set_nodelay(self, sock):
1836+
opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
1837+
self.assertFalse(opt)
1838+
1839+
_set_nodelay(sock)
1840+
1841+
opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
1842+
self.assertTrue(opt)
1843+
1844+
@unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'),
1845+
'need socket.TCP_NODELAY')
1846+
def test_set_nodelay(self):
1847+
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM,
1848+
proto=socket.IPPROTO_TCP)
1849+
with sock:
1850+
self.check_set_nodelay(sock)
1851+
1852+
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM,
1853+
proto=socket.IPPROTO_TCP)
1854+
with sock:
1855+
sock.setblocking(False)
1856+
self.check_set_nodelay(sock)
1857+
1858+
18321859
if __name__ == '__main__':
18331860
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)