Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 improve outgoing port reuse in same process execution flow (sync&async) #176

Merged
merged 1 commit into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2.11.910 (2024-11-08)
=====================

- Improved reliability of reusing a specific outgoing port. The feature is no longer experimental.

2.11.909 (2024-11-07)
=====================

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.909"
__version__ = "2.11.910"
8 changes: 8 additions & 0 deletions src/urllib3/contrib/resolver/_async/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import ipaddress
import socket
import struct
import typing
from abc import ABCMeta, abstractmethod
from datetime import datetime, timedelta, timezone
Expand Down Expand Up @@ -133,6 +134,13 @@ async def create_connection( # type: ignore[override]
): # Defensive: we can't do anything better than this.
pass

try:
sock.setsockopt(
socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0)
)
except (OSError, AttributeError):
pass

# If provided, set socket level options before connecting.
_set_socket_options(sock, socket_options)

Expand Down
7 changes: 7 additions & 0 deletions src/urllib3/contrib/resolver/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ def create_connection(
): # Defensive: we can't do anything better than this.
pass

try:
sock.setsockopt(
socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0)
)
except (OSError, AttributeError):
pass

sock.bind(source_address)

# If provided, set socket level options before connecting.
Expand Down
9 changes: 4 additions & 5 deletions src/urllib3/contrib/ssa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import typing
import warnings

SHUT_RD = 0 # taken from the "_socket" module
StandardTimeoutError = socket.timeout

try:
Expand Down Expand Up @@ -126,7 +125,7 @@ def close(self) -> None:

if hasattr(self._sock, "shutdown"):
try:
self._sock.shutdown(SHUT_RD)
self._sock.shutdown(socket.SHUT_RD)
shutdown_called = True
except TypeError:
uvloop_edge_case_bug = True
Expand All @@ -143,7 +142,7 @@ def close(self) -> None:
pass
else:
try:
direct_sock.shutdown(SHUT_RD)
direct_sock.shutdown(socket.SHUT_RD)
shutdown_called = True
except OSError:
warnings.warn(
Expand Down Expand Up @@ -174,7 +173,7 @@ def close(self) -> None:
and not _CPYTHON_SELECTOR_CLOSE_BUG_EXIST
):
try:
self._sock._sock.detach()
self._sock._sock.close()
except (AttributeError, OSError):
pass

Expand All @@ -198,7 +197,7 @@ def close(self) -> None:
async def wait_for_readiness(self) -> None:
await self._established.wait()

def setsockopt(self, __level: int, __optname: int, __value: int) -> None:
def setsockopt(self, __level: int, __optname: int, __value: int | bytes) -> None:
self._sock.setsockopt(__level, __optname, __value)

def getsockopt(self, __level: int, __optname: int) -> int:
Expand Down
2 changes: 1 addition & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def requires_traefik() -> None:

try:
sock = socket.create_connection(
(os.environ.get("TRAEFIK_HTTPBIN_IPV4", "127.0.0.1"), 8888), timeout=0.3
(os.environ.get("TRAEFIK_HTTPBIN_IPV4", "127.0.0.1"), 8888), timeout=1
)
except (ConnectionRefusedError, socket.gaierror, TimeoutError):
_TRAEFIK_AVAILABLE = False
Expand Down
3 changes: 0 additions & 3 deletions test/with_traefik/asynchronous/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,6 @@ async def test_quic_extract_ssl_ctx_ca_root(self) -> None:

await conn.close()

@pytest.mark.xfail(
reason="experimental support for reusable outgoing port", strict=False
)
async def test_fast_reuse_outgoing_port(self) -> None:
for _ in range(4):
conn = AsyncHTTPSConnection(
Expand Down
3 changes: 0 additions & 3 deletions test/with_traefik/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,6 @@ def test_quic_extract_ssl_ctx_ca_root(self) -> None:

conn.close()

@pytest.mark.xfail(
reason="experimental support for reusable outgoing port", strict=False
)
def test_fast_reuse_outgoing_port(self) -> None:
for _ in range(4):
conn = HTTPSConnection(
Expand Down
Loading