Skip to content

Commit

Permalink
Bugfix uvloop and mac rst (#168)
Browse files Browse the repository at this point in the history
2.11.905 (2024-10-26)
=====================

- Fixed custom loop like uvloop needing advanced error handling on
transport close.
- Fixed MacOS connection reset by peer handling to detect connection
close (continuation of fix in 2.11.902)
  • Loading branch information
Ousret authored Oct 26, 2024
2 parents 7640299 + 29ea44b commit d8f21eb
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 9 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
2.11.905 (2024-10-26)
=====================

- Fixed custom loop like uvloop needing advanced error handling on transport close.
- Fixed MacOS connection reset by peer handling to detect connection close (continuation of fix in 2.11.902)

2.11.904 (2024-10-25)
=====================

Expand Down
2 changes: 1 addition & 1 deletion src/urllib3/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file is protected via CODEOWNERS
from __future__ import annotations

__version__ = "2.11.904"
__version__ = "2.11.905"
12 changes: 10 additions & 2 deletions src/urllib3/backend/_async/hface.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ async def peek_and_react(self) -> bool:
peek_data = await self.sock.recv(self.blocksize)
except SocketTimeout:
return False
except ConnectionAbortedError:
except (ConnectionAbortedError, ConnectionResetError):
peek_data = b""
finally:
self.sock.settimeout(bck_timeout)
Expand Down Expand Up @@ -815,7 +815,15 @@ async def __exchange_until(

try:
data_in = await self.sock.recv(self.blocksize)
except ConnectionAbortedError:
except (ConnectionAbortedError, ConnectionResetError) as e:
if isinstance(e, ConnectionResetError) and (
event_type is HandshakeCompleted
or (
isinstance(event_type, tuple)
and HandshakeCompleted in event_type
)
):
raise e
data_in = b""

reach_socket = True
Expand Down
14 changes: 11 additions & 3 deletions src/urllib3/backend/hface.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ def peek_and_react(self) -> bool:
This method return True if there is any event ready to unpack for the connection.
Some server implementation may be aggressive toward "idle" session
this is especially true when using QUIC.
For example, google/quiche expect regular ACKs, otherwise will
For example, google/quiche send regular unsolicited data and expect regular ACKs, otherwise will
deduct that network conn is dead.
see: https://github.com/google/quiche/commit/c4bb0723f0a03e135bc9328b59a39382761f3de6
https://github.com/google/quiche/blob/92b45f743288ea2f43ae8cdc4a783ef252e41d93/quiche/quic/core/quic_connection.cc#L6322
Expand All @@ -786,7 +786,7 @@ def peek_and_react(self) -> bool:
peek_data = self.sock.recv(self.blocksize)
except (OSError, TimeoutError, socket.timeout):
return False
except ConnectionAbortedError:
except (ConnectionAbortedError, ConnectionResetError):
peek_data = b""
finally:
self.sock.settimeout(bck_timeout)
Expand Down Expand Up @@ -866,7 +866,15 @@ def __exchange_until(

try:
data_in = self.sock.recv(self.blocksize)
except ConnectionAbortedError: # on Windows, mostly.
except (ConnectionAbortedError, ConnectionResetError) as e:
if isinstance(e, ConnectionResetError) and (
event_type is HandshakeCompleted
or (
isinstance(event_type, tuple)
and HandshakeCompleted in event_type
)
):
raise e
data_in = b""

reach_socket = True
Expand Down
37 changes: 34 additions & 3 deletions src/urllib3/contrib/ssa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import platform
import socket
import typing
import warnings

SHUT_RD = 0 # taken from the "_socket" module
StandardTimeoutError = socket.timeout
Expand Down Expand Up @@ -77,18 +78,48 @@ def close(self) -> None:
self._writer.close()

try:
# see https://github.com/MagicStack/uvloop/issues/241
# and https://github.com/jawah/niquests/issues/166
# probably not just uvloop.
uvloop_edge_case_bug = False

if hasattr(self._sock, "shutdown"):
try:
self._sock.shutdown(SHUT_RD)
except TypeError: # uvloop don't support shutdown!
if hasattr(self._sock, "close"):
except TypeError:
uvloop_edge_case_bug = True
# uvloop don't support shutdown! and sometime does not support close()...
# see https://github.com/jawah/niquests/issues/166 for ctx.
try:
self._sock.close()
except TypeError:
# last chance of releasing properly the underlying fd!
try:
direct_sock = socket.socket(fileno=self._sock.fileno())
except OSError:
pass
else:
try:
direct_sock.shutdown(SHUT_RD)
except OSError:
warnings.warn(
(
"urllib3-future is unable to properly close your async socket. "
"This mean that you are probably using an asyncio implementation like uvloop "
"that does not support shutdown() or/and close() on the socket transport. "
"This will lead to unclosed socket (fd)."
),
ResourceWarning,
)
finally:
direct_sock.detach()
elif hasattr(self._sock, "close"):
self._sock.close()
# we have to force call close() on our sock object in UDP ctx. (even after shutdown)
# or we'll get a resource warning for sure!
if self.type == socket.SOCK_DGRAM and hasattr(self._sock, "close"):
self._sock.close()
if not uvloop_edge_case_bug:
self._sock.close()
except OSError:
pass

Expand Down

0 comments on commit d8f21eb

Please sign in to comment.