Issue #9792: In case of connection failure, socket.create_connection()
would swallow the exception and raise a new one, making it impossible
to fetch the original errno, or to filter timeout errors. Now the
original error is re-raised.
diff --git a/Lib/socket.py b/Lib/socket.py
index 89fe36e..004d6a9 100644
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -297,8 +297,8 @@
An host of '' or port 0 tells the OS to use the default.
"""
- msg = "getaddrinfo returns an empty list"
host, port = address
+ err = None
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
@@ -311,9 +311,12 @@
sock.connect(sa)
return sock
- except error as err:
- msg = err
+ except error as _:
+ err = _
if sock is not None:
sock.close()
- raise error(msg)
+ if err is not None:
+ raise err
+ else:
+ raise error("getaddrinfo returns an empty list")
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 2f6d007..19c494b 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -13,6 +13,7 @@
import sys
import os
import array
+import contextlib
from weakref import proxy
import signal
@@ -1203,12 +1204,42 @@
class NetworkConnectionNoServer(unittest.TestCase):
- def testWithoutServer(self):
+ class MockSocket(socket.socket):
+ def connect(self, *args):
+ raise socket.timeout('timed out')
+
+ @contextlib.contextmanager
+ def mocked_socket_module(self):
+ """Return a socket which times out on connect"""
+ old_socket = socket.socket
+ socket.socket = self.MockSocket
+ try:
+ yield
+ finally:
+ socket.socket = old_socket
+
+ def test_connect(self):
port = support.find_unused_port()
- self.assertRaises(
- socket.error,
- lambda: socket.create_connection((HOST, port))
- )
+ cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ with self.assertRaises(socket.error) as cm:
+ cli.connect((HOST, port))
+ self.assertEqual(cm.exception.errno, errno.ECONNREFUSED)
+
+ def test_create_connection(self):
+ # Issue #9792: errors raised by create_connection() should have
+ # a proper errno attribute.
+ port = support.find_unused_port()
+ with self.assertRaises(socket.error) as cm:
+ socket.create_connection((HOST, port))
+ self.assertEqual(cm.exception.errno, errno.ECONNREFUSED)
+
+ def test_create_connection_timeout(self):
+ # Issue #9792: create_connection() should not recast timeout errors
+ # as generic socket errors.
+ with self.mocked_socket_module():
+ with self.assertRaises(socket.timeout):
+ socket.create_connection((HOST, 1234))
+
@unittest.skipUnless(thread, 'Threading required for this test.')
class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):