summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xDoc/library/socket.rst11
-rw-r--r--Doc/whatsnew/3.11.rst4
-rwxr-xr-xLib/socket.py24
-rwxr-xr-xLib/test/test_socket.py18
-rw-r--r--Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst1
5 files changed, 48 insertions, 10 deletions
diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index f8392ba..d7a4401 100755
--- a/Doc/library/socket.rst
+++ b/Doc/library/socket.rst
@@ -660,7 +660,7 @@ The following functions all create :ref:`socket objects <socket-objects>`.
Windows support added.
-.. function:: create_connection(address[, timeout[, source_address]])
+.. function:: create_connection(address[, timeout[, source_address[, all_errors]]])
Connect to a TCP service listening on the internet *address* (a 2-tuple
``(host, port)``), and return the socket object. This is a higher-level
@@ -679,9 +679,18 @@ The following functions all create :ref:`socket objects <socket-objects>`.
socket to bind to as its source address before connecting. If host or port
are '' or 0 respectively the OS default behavior will be used.
+ When a connection cannot be created, an exception is raised. By default,
+ it is the exception from the last address in the list. If *all_errors*
+ is ``True``, it is an :exc:`ExceptionGroup` containing the errors of all
+ attempts.
+
.. versionchanged:: 3.2
*source_address* was added.
+ .. versionchanged:: 3.11
+ *all_errors* was added.
+
+
.. function:: create_server(address, *, family=AF_INET, backlog=None, reuse_port=False, dualstack_ipv6=False)
Convenience function which creates a TCP socket bound to *address* (a 2-tuple
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 27a5a0f..baff687 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -357,6 +357,10 @@ socket
* Add CAN Socket support for NetBSD.
(Contributed by Thomas Klausner in :issue:`30512`.)
+* :meth:`~socket.create_connection` has an option to raise, in case of
+ failure to connect, an :exc:`ExceptionGroup` containing all errors
+ instead of only raising the last error.
+ (Contributed by Irit Katriel in :issue:`29980`).
sqlite3
-------
diff --git a/Lib/socket.py b/Lib/socket.py
index ef82c49..97362d9 100755
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -806,7 +806,7 @@ def getfqdn(name=''):
_GLOBAL_DEFAULT_TIMEOUT = object()
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
- source_address=None):
+ source_address=None, all_errors=False):
"""Connect to *address* and return the socket object.
Convenience function. Connect to *address* (a 2-tuple ``(host,
@@ -816,11 +816,13 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
global default timeout setting returned by :func:`getdefaulttimeout`
is used. If *source_address* is set it must be a tuple of (host, port)
for the socket to bind as a source address before making the connection.
- A host of '' or port 0 tells the OS to use the default.
+ A host of '' or port 0 tells the OS to use the default. When a connection
+ cannot be created, raises the last error if *all_errors* is False,
+ and an ExceptionGroup of all errors if *all_errors* is True.
"""
host, port = address
- err = None
+ exceptions = []
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
@@ -832,20 +834,24 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
sock.bind(source_address)
sock.connect(sa)
# Break explicitly a reference cycle
- err = None
+ exceptions.clear()
return sock
- except error as _:
- err = _
+ except error as exc:
+ if not all_errors:
+ exceptions.clear() # raise only the last error
+ exceptions.append(exc)
if sock is not None:
sock.close()
- if err is not None:
+ if len(exceptions):
try:
- raise err
+ if not all_errors:
+ raise exceptions[0]
+ raise ExceptionGroup("create_connection failed", exceptions)
finally:
# Break explicitly a reference cycle
- err = None
+ exceptions = None
else:
raise error("getaddrinfo returns an empty list")
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 97cc626..6133637 100755
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -5177,6 +5177,24 @@ class NetworkConnectionNoServer(unittest.TestCase):
expected_errnos = socket_helper.get_socket_conn_refused_errs()
self.assertIn(cm.exception.errno, expected_errnos)
+ def test_create_connection_all_errors(self):
+ port = socket_helper.find_unused_port()
+ try:
+ socket.create_connection((HOST, port), all_errors=True)
+ except ExceptionGroup as e:
+ eg = e
+ else:
+ self.fail('expected connection to fail')
+
+ self.assertIsInstance(eg, ExceptionGroup)
+ for e in eg.exceptions:
+ self.assertIsInstance(e, OSError)
+
+ addresses = socket.getaddrinfo(
+ 'localhost', port, 0, socket.SOCK_STREAM)
+ # assert that we got an exception for each address
+ self.assertEqual(len(addresses), len(eg.exceptions))
+
def test_create_connection_timeout(self):
# Issue #9792: create_connection() should not recast timeout errors
# as generic socket errors.
diff --git a/Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst b/Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst
new file mode 100644
index 0000000..ddd4eb7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst
@@ -0,0 +1 @@
+Add option to raise all errors from :meth:`~socket.create_connection` in an :exc:`ExceptionGroup` when it fails to create a connection. The default remains to raise only the last error that had occurred when multiple addresses were tried.