From 62c3171ec9cb142b20aee3962a2f8f1f0e02d12d Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 4 Nov 2026 06:55:12 +0100 Subject: [PATCH] gh-161076: Ignore setsockopt(TCP_NODELAY) errors in asyncio (GH-161253) (cherry picked from commit 405f12722cb7631f15d5b6df5825a2262209d9d8) Co-authored-by: Andrew Svetlov --- Lib/asyncio/base_events.py | 8 +++++++- Lib/test/test_asyncio/test_base_events.py | 11 +++++++++++ .../2026-11-03-12-00-00.gh-issue-161076.nOdLy1.rst | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-11-03-12-00-00.gh-issue-161076.nOdLy1.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 59c61f9ba657a12..a2aa304f868343b 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -195,7 +195,13 @@ def _set_nodelay(sock): if (sock.family in {socket.AF_INET, socket.AF_INET6} and sock.type == socket.SOCK_STREAM and sock.proto == socket.IPPROTO_TCP): - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + try: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except OSError: + # TCP_NODELAY is only a latency optimization. It can fail + # on a connection already reset by the peer (e.g. EINVAL + # on macOS); the error will surface on the next I/O. + pass else: def _set_nodelay(sock): pass diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 01529a9a2c1a602..68abcb026ad38e9 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -2426,6 +2426,17 @@ def test_set_nodelay(self): sock.setblocking(False) self.check_set_nodelay(sock) + @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), + 'need socket.TCP_NODELAY') + def test_set_nodelay_error(self): + # gh-161076: a failing setsockopt() must not break transport creation. + sock = mock.Mock(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + sock.setsockopt.side_effect = OSError(errno.EINVAL, 'Invalid argument') + base_events._set_nodelay(sock) + sock.setsockopt.assert_called_once_with( + socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if __name__ == '__main__': diff --git a/Misc/NEWS.d/next/Library/2026-11-03-12-00-00.gh-issue-161076.nOdLy1.rst b/Misc/NEWS.d/next/Library/2026-11-03-12-00-00.gh-issue-161076.nOdLy1.rst new file mode 100644 index 000000000000000..bc286d0a676c024 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-03-12-00-00.gh-issue-161076.nOdLy1.rst @@ -0,0 +1,5 @@ +Fix :mod:`asyncio` leaving a half-created transport registered with the +event loop and the server when setting ``TCP_NODELAY`` on a socket fails. +Such a failure could make :meth:`asyncio.Server.wait_closed` hang and +prevent new connections from being served. Errors from +``setsockopt(TCP_NODELAY)`` are now ignored.