mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
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:
1 parent
1ee9a29062
commit
fd5b30eb5e
5 files changed
+318
-102
No files matched your search
Vendored
+79
-70
@@ -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(
|
||||
|
||||
Vendored
+10
-4
@@ -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}")
|
||||
Vendored
+88
-3
@@ -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
|
||||
|
||||
Vendored
+18
@@ -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()
|
||||
Reference in new issue
Block a user