Align socket and socketserver with Python 3.15 (#9156)

Import the canonical socket libraries and tests through update_lib, preserving the existing AF_ALG expected failures. Match setsockopt index/buffer conversion and error ordering, mask optlen with the final CPython deprecation warning, reject oversized Windows buffers before the host call, and preserve arbitrary-precision getaddrinfo ports and empty canonical names.

Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-16 10:14:55 +09:00
1 parent 1ee9a29062
commit fd5b30eb5e
5 files changed
+318 -102

No files matched your search

+79 -70
View File
@@ -56,6 +56,7 @@ import io
import os
import sys
from enum import IntEnum, IntFlag
from functools import partial
try:
import errno
@@ -122,7 +123,7 @@ if sys.platform.lower().startswith("win"):
10004: "The operation was interrupted.",
10009: "A bad file handle was passed.",
10013: "Permission denied.",
10014: "A fault occurred on the network??",
10014: "An invalid pointer was passed.",
10022: "An invalid operation was attempted.",
10024: "Too many open files.",
10035: "The socket operation would block.",
@@ -180,7 +181,7 @@ if sys.platform.lower().startswith("win"):
11001: "Host not found.",
11002: "Nonauthoritative host not found.",
11003: "This is a nonrecoverable error.",
11004: "Valid name, no data record requested type.",
11004: "Valid name, no data record of requested type.",
11005: "QoS receivers.",
11006: "QoS senders.",
11007: "No QoS senders.",
@@ -196,7 +197,7 @@ if sys.platform.lower().startswith("win"):
11017: "QoS flowspec error.",
11018: "Invalid QoS provider buffer.",
11019: "Invalid QoS filter style.",
11020: "Invalid QoS filter style.",
11020: "Invalid QoS filter type.",
11021: "Incorrect QoS filter count.",
11022: "Invalid QoS object length.",
11023: "Incorrect QoS flow count.",
@@ -348,75 +349,83 @@ class socket(_socket.socket):
text.mode = mode
return text
def _sendfile_zerocopy(self, zerocopy_func, giveup_exc_type, file,
offset=0, count=None):
"""
Send a file using a zero-copy function.
"""
import selectors
self._check_sendfile_params(file, offset, count)
sockno = self.fileno()
try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err:
raise giveup_exc_type(err) # not a regular file
try:
fsize = os.fstat(fileno).st_size
except OSError as err:
raise giveup_exc_type(err) # not a regular file
if not fsize:
return 0 # empty file
# Truncate to 1GiB to avoid OverflowError, see bpo-38319.
blocksize = min(count or fsize, 2 ** 30)
timeout = self.gettimeout()
if timeout == 0:
raise ValueError("non-blocking sockets are not supported")
# poll/select have the advantage of not requiring any
# extra file descriptor, contrarily to epoll/kqueue
# (also, they require a single syscall).
if hasattr(selectors, 'PollSelector'):
selector = selectors.PollSelector()
else:
selector = selectors.SelectSelector()
selector.register(sockno, selectors.EVENT_WRITE)
total_sent = 0
# localize variable access to minimize overhead
selector_select = selector.select
try:
while True:
if timeout and not selector_select(timeout):
raise TimeoutError('timed out')
if count:
blocksize = min(count - total_sent, blocksize)
if blocksize <= 0:
break
try:
sent = zerocopy_func(fileno, offset, blocksize)
except BlockingIOError:
if not timeout:
# Block until the socket is ready to send some
# data; avoids hogging CPU resources.
selector_select()
continue
except OSError as err:
if total_sent == 0:
# We can get here for different reasons, the main
# one being 'file' is not a regular mmap(2)-like
# file, in which case we'll fall back on using
# plain send().
raise giveup_exc_type(err)
raise err from None
else:
if sent == 0:
break # EOF
offset += sent
total_sent += sent
return total_sent
finally:
if total_sent > 0 and hasattr(file, 'seek'):
file.seek(offset)
if hasattr(os, 'sendfile'):
def _sendfile_use_sendfile(self, file, offset=0, count=None):
# Lazy import to improve module import time
import selectors
self._check_sendfile_params(file, offset, count)
sockno = self.fileno()
try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err:
raise _GiveupOnSendfile(err) # not a regular file
try:
fsize = os.fstat(fileno).st_size
except OSError as err:
raise _GiveupOnSendfile(err) # not a regular file
if not fsize:
return 0 # empty file
# Truncate to 1GiB to avoid OverflowError, see bpo-38319.
blocksize = min(count or fsize, 2 ** 30)
timeout = self.gettimeout()
if timeout == 0:
raise ValueError("non-blocking sockets are not supported")
# poll/select have the advantage of not requiring any
# extra file descriptor, contrarily to epoll/kqueue
# (also, they require a single syscall).
if hasattr(selectors, 'PollSelector'):
selector = selectors.PollSelector()
else:
selector = selectors.SelectSelector()
selector.register(sockno, selectors.EVENT_WRITE)
total_sent = 0
# localize variable access to minimize overhead
selector_select = selector.select
os_sendfile = os.sendfile
try:
while True:
if timeout and not selector_select(timeout):
raise TimeoutError('timed out')
if count:
blocksize = min(count - total_sent, blocksize)
if blocksize <= 0:
break
try:
sent = os_sendfile(sockno, fileno, offset, blocksize)
except BlockingIOError:
if not timeout:
# Block until the socket is ready to send some
# data; avoids hogging CPU resources.
selector_select()
continue
except OSError as err:
if total_sent == 0:
# We can get here for different reasons, the main
# one being 'file' is not a regular mmap(2)-like
# file, in which case we'll fall back on using
# plain send().
raise _GiveupOnSendfile(err)
raise err from None
else:
if sent == 0:
break # EOF
offset += sent
total_sent += sent
return total_sent
finally:
if total_sent > 0 and hasattr(file, 'seek'):
file.seek(offset)
return self._sendfile_zerocopy(
partial(os.sendfile, self.fileno()),
_GiveupOnSendfile,
file, offset, count,
)
else:
def _sendfile_use_sendfile(self, file, offset=0, count=None):
raise _GiveupOnSendfile(
+10 -4
View File
@@ -120,9 +120,6 @@ BaseServer:
# Author of the BaseServer patch: Luke Kenneth Casson Leighton
__version__ = "0.4"
import socket
import selectors
import os
@@ -441,7 +438,7 @@ class TCPServer(BaseServer):
socket_type = socket.SOCK_STREAM
request_queue_size = 5
request_queue_size = getattr(socket, "SOMAXCONN", 5)
allow_reuse_address = False
@@ -861,3 +858,12 @@ class DatagramRequestHandler(BaseRequestHandler):
def finish(self):
self.socket.sendto(self.wfile.getvalue(), self.client_address)
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "0.4" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+88 -3
View File
@@ -8,7 +8,9 @@ from test.support.import_helper import ensure_lazy_imports
import _thread as thread
import array
import contextlib
import decimal
import errno
import fractions
import gc
import io
import itertools
@@ -1349,10 +1351,20 @@ class GeneralModuleTests(unittest.TestCase):
self.assertEqual(s.gettimeout(), None)
# Set the default timeout to 10, and see if it propagates
with socket_setdefaulttimeout(10):
self.assertEqual(socket.getdefaulttimeout(), 10)
with socket_setdefaulttimeout(10.125):
self.assertEqual(socket.getdefaulttimeout(), 10.125)
with socket.socket() as sock:
self.assertEqual(sock.gettimeout(), 10)
self.assertEqual(sock.gettimeout(), 10.125)
socket.setdefaulttimeout(decimal.Decimal('11.125'))
self.assertEqual(socket.getdefaulttimeout(), 11.125)
with socket.socket() as sock:
self.assertEqual(sock.gettimeout(), 11.125)
socket.setdefaulttimeout(fractions.Fraction(97, 8))
self.assertEqual(socket.getdefaulttimeout(), 12.125)
with socket.socket() as sock:
self.assertEqual(sock.gettimeout(), 12.125)
# Reset the default timeout to None, and see if it propagates
socket.setdefaulttimeout(None)
@@ -1580,6 +1592,40 @@ class GeneralModuleTests(unittest.TestCase):
reuse = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
self.assertFalse(reuse == 0, "failed to set reuse mode")
def test_setsockopt_errors(self):
# See issue #107546.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.addCleanup(sock.close)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # No error expected.
with self.assertRaises(OverflowError):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 2 ** 100)
with self.assertRaises(OverflowError):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, - 2 ** 100)
with self.assertRaises(OverflowError):
sock.setsockopt(socket.SOL_SOCKET, 2 ** 100, 1)
with self.assertRaises(OverflowError):
sock.setsockopt(2 ** 100, socket.SO_REUSEADDR, 1)
with self.assertRaisesRegex(TypeError, "socket option should be int, bytes-like object or None"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, dict())
with self.assertRaisesRegex(TypeError, "requires 4 arguments when the third argument is None"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, None)
with self.assertRaisesRegex(TypeError, "only takes 4 arguments when the third argument is None"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1, 2)
with self.assertRaisesRegex(TypeError, "takes at least 3 arguments"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
with self.assertRaisesRegex(TypeError, "takes at most 4 arguments"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1, 2, 3)
def testSendAfterClose(self):
# testing send() after close() with timeout
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
@@ -2435,6 +2481,45 @@ class ISOTPTest(unittest.TestCase):
socket.CAN_ISOTP
socket.SOCK_DGRAM
@unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"),
"missing <linux/can/isotp.h>")
def testISOTP(self):
socket.SOL_CAN_ISOTP
socket.CAN_ISOTP_OPTS
socket.CAN_ISOTP_RECV_FC
socket.CAN_ISOTP_TX_STMIN
socket.CAN_ISOTP_RX_STMIN
socket.CAN_ISOTP_LL_OPTS
socket.CAN_ISOTP_LISTEN_MODE
socket.CAN_ISOTP_EXTEND_ADDR
socket.CAN_ISOTP_TX_PADDING
socket.CAN_ISOTP_RX_PADDING
socket.CAN_ISOTP_CHK_PAD_LEN
socket.CAN_ISOTP_CHK_PAD_DATA
socket.CAN_ISOTP_HALF_DUPLEX
socket.CAN_ISOTP_FORCE_TXSTMIN
socket.CAN_ISOTP_FORCE_RXSTMIN
socket.CAN_ISOTP_RX_EXT_ADDR
socket.CAN_ISOTP_WAIT_TX_DONE
# This constant is not always available
# socket.CAN_ISOTP_SF_BROADCAST
socket.CAN_ISOTP_DEFAULT_FLAGS
socket.CAN_ISOTP_DEFAULT_EXT_ADDRESS
socket.CAN_ISOTP_DEFAULT_PAD_CONTENT
socket.CAN_ISOTP_DEFAULT_FRAME_TXTIME
socket.CAN_ISOTP_DEFAULT_RECV_BS
socket.CAN_ISOTP_DEFAULT_EXT_ADDRESS
socket.CAN_ISOTP_DEFAULT_RECV_STMIN
socket.CAN_ISOTP_DEFAULT_RECV_WFTMAX
socket.CAN_ISOTP_DEFAULT_LL_MTU
socket.CAN_ISOTP_DEFAULT_LL_TX_DL
socket.CAN_ISOTP_DEFAULT_LL_TX_FLAGS
def testCreateSocket(self):
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
pass
+18
View File
@@ -17,6 +17,7 @@ from test.support import reap_children, verbose
from test.support import os_helper
from test.support import socket_helper
from test.support import threading_helper
from test.support import warnings_helper
test.support.requires("network")
@@ -43,6 +44,7 @@ def receive(sock, n, timeout=test.support.SHORT_TIMEOUT):
raise RuntimeError("timed out on %r" % (sock,))
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@test.support.requires_fork()
@contextlib.contextmanager
def simple_subprocess(testcase):
@@ -173,6 +175,7 @@ class SocketServerTest(unittest.TestCase):
socketserver.StreamRequestHandler,
self.stream_examine)
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@requires_forking
def test_ForkingTCPServer(self):
with simple_subprocess(self):
@@ -192,6 +195,7 @@ class SocketServerTest(unittest.TestCase):
socketserver.StreamRequestHandler,
self.stream_examine)
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@requires_unix_sockets
@requires_forking
def test_ForkingUnixStreamServer(self):
@@ -210,6 +214,7 @@ class SocketServerTest(unittest.TestCase):
socketserver.DatagramRequestHandler,
self.dgram_examine)
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@requires_forking
def test_ForkingUDPServer(self):
with simple_subprocess(self):
@@ -233,6 +238,7 @@ class SocketServerTest(unittest.TestCase):
socketserver.DatagramRequestHandler,
self.dgram_examine)
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@requires_unix_sockets
@requires_forking
def test_ForkingUnixDatagramServer(self):
@@ -318,11 +324,13 @@ class ErrorHandlerTest(unittest.TestCase):
self.assertIs(cm.exc_type, SystemExit)
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@requires_forking
def test_forking_handled(self):
ForkingErrorTestServer(ValueError)
self.check_result(handled=True)
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@requires_forking
def test_forking_not_handled(self):
ForkingErrorTestServer(SystemExit)
@@ -507,5 +515,15 @@ class MiscTestCase(unittest.TestCase):
server.server_close()
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(socketserver, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
unittest.main()