diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index ed47f757a26144..b9f2df3cd8f630 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -193,7 +193,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 a7f89608b5abb7..61ad949a2b195b 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -2330,6 +2330,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 00000000000000..bc286d0a676c02 --- /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.