diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 7eb55bd63ddb73..3dc9d765b0a37a 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -858,6 +858,10 @@ def loop(f=None): if self.is_closed(): return f = self._proactor.accept(sock) + except exceptions.CancelledError: + # Effectively ignore connections that throw a cancelled error + # during setup, loop back around and continue serving. + sock.close() except OSError as exc: if sock.fileno() != -1: self.call_exception_handler({ diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index bf99bc271c7acd..1c70fc7559ddb8 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -414,6 +414,24 @@ async def _make_subprocess_transport(self, protocol, args, shell, return transp +def overlapped_connection_reset_error_handler(func): + """ + Rethrow common connection errors that come from clients + disconnecting unexpectedly. This is a common error that + can be safely ignored in most cases. + """ + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except OSError as exc: + if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED, + _overlapped.ERROR_OPERATION_ABORTED): + raise ConnectionResetError(*exc.args) + else: + raise + return wrapper + + class IocpProactor: """Proactor implementation using IOCP.""" @@ -458,15 +476,9 @@ def _result(self, value): return fut @staticmethod + @overlapped_connection_reset_error_handler def finish_socket_func(trans, key, ov): - try: - return ov.getresult() - except OSError as exc: - if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED, - _overlapped.ERROR_OPERATION_ABORTED): - raise ConnectionResetError(*exc.args) - else: - raise + return ov.getresult() @classmethod def _finish_recvfrom(cls, trans, key, ov, *, empty_result): @@ -552,6 +564,7 @@ def accept(self, listener): ov = _overlapped.Overlapped(NULL) ov.AcceptEx(listener.fileno(), conn.fileno()) + @overlapped_connection_reset_error_handler def finish_accept(trans, key, ov): ov.getresult() # Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work. @@ -596,6 +609,7 @@ def connect(self, conn, address): ov = _overlapped.Overlapped(NULL) ov.ConnectEx(conn.fileno(), address) + @overlapped_connection_reset_error_handler def finish_connect(trans, key, ov): ov.getresult() # Use SO_UPDATE_CONNECT_CONTEXT so getsockname() etc work. @@ -628,6 +642,7 @@ def accept_pipe(self, pipe): # completion of the connection. return self._result(pipe) + @overlapped_connection_reset_error_handler def finish_accept_pipe(trans, key, ov): ov.getresult() return pipe diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-30-03-17-49.gh-issue-93821.5L0mkX.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-30-03-17-49.gh-issue-93821.5L0mkX.rst new file mode 100644 index 00000000000000..fdbcce5dd92e7a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-30-03-17-49.gh-issue-93821.5L0mkX.rst @@ -0,0 +1 @@ +Fix error handling in windows events where clients terminating connections could result in an :mod:`asyncio` server using Proactor event loops to hang indefinitely.