Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2877346

Browse files
committed
ayncio, Tulip issue 129: BaseEventLoop.sock_connect() now raises an error if
the address is not resolved (hostname instead of an IP address) for AF_INET and AF_INET6 address families.
1 parent 7dfaa27 commit 2877346

5 files changed

Lines changed: 59 additions & 13 deletions

File tree

Doc/library/asyncio-eventloop.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,12 @@ Low-level socket operations
366366

367367
Connect to a remote socket at *address*.
368368

369+
The *address* must be already resolved to avoid the trap of hanging the
370+
entire event loop when the address requires doing a DNS lookup. For
371+
example, it must be an IP address, not an hostname, for
372+
:py:data:`~socket.AF_INET` and :py:data:`~socket.AF_INET6` address families.
373+
Use :meth:`getaddrinfo` to resolve the hostname asynchronously.
374+
369375
This method returns a :ref:`coroutine object <coroutine>`.
370376

371377
.. seealso::

Lib/asyncio/base_events.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,31 @@ class _StopError(BaseException):
4141
"""Raised to stop the event loop."""
4242

4343

44+
def _check_resolved_address(sock, address):
45+
# Ensure that the address is already resolved to avoid the trap of hanging
46+
# the entire event loop when the address requires doing a DNS lookup.
47+
family = sock.family
48+
if family not in (socket.AF_INET, socket.AF_INET6):
49+
return
50+
51+
host, port = address
52+
type_mask = 0
53+
if hasattr(socket, 'SOCK_NONBLOCK'):
54+
type_mask |= socket.SOCK_NONBLOCK
55+
if hasattr(socket, 'SOCK_CLOEXEC'):
56+
type_mask |= socket.SOCK_CLOEXEC
57+
# Use getaddrinfo(AI_NUMERICHOST) to ensure that the address is
58+
# already resolved.
59+
try:
60+
socket.getaddrinfo(host, port,
61+
family=family,
62+
type=(sock.type & ~type_mask),
63+
proto=sock.proto,
64+
flags=socket.AI_NUMERICHOST)
65+
except socket.gaierror as err:
66+
raise ValueError("address must be resolved (IP address), got %r: %s"
67+
% (address, err))
68+
4469
def _raise_stop_error(*args):
4570
raise _StopError
4671

Lib/asyncio/proactor_events.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,14 @@ def sock_sendall(self, sock, data):
404404
return self._proactor.send(sock, data)
405405

406406
def sock_connect(self, sock, address):
407-
return self._proactor.connect(sock, address)
407+
try:
408+
base_events._check_resolved_address(sock, address)
409+
except ValueError as err:
410+
fut = futures.Future(loop=self)
411+
fut.set_exception(err)
412+
return fut
413+
else:
414+
return self._proactor.connect(sock, address)
408415

409416
def sock_accept(self, sock):
410417
return self._proactor.accept(sock)

Lib/asyncio/selector_events.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ def sock_recv(self, sock, n):
208208
return fut
209209

210210
def _sock_recv(self, fut, registered, sock, n):
211+
# _sock_recv() can add itself as an I/O callback if the operation can't
212+
# be done immediatly. Don't use it directly, call sock_recv().
211213
fd = sock.fileno()
212214
if registered:
213215
# Remove the callback early. It should be rare that the
@@ -260,22 +262,16 @@ def _sock_sendall(self, fut, registered, sock, data):
260262

261263
def sock_connect(self, sock, address):
262264
"""XXX"""
263-
# That address better not require a lookup! We're not calling
264-
# self.getaddrinfo() for you here. But verifying this is
265-
# complicated; the socket module doesn't have a pattern for
266-
# IPv6 addresses (there are too many forms, apparently).
267265
fut = futures.Future(loop=self)
268-
self._sock_connect(fut, False, sock, address)
266+
try:
267+
base_events._check_resolved_address(sock, address)
268+
except ValueError as err:
269+
fut.set_exception(err)
270+
else:
271+
self._sock_connect(fut, False, sock, address)
269272
return fut
270273

271274
def _sock_connect(self, fut, registered, sock, address):
272-
# TODO: Use getaddrinfo() to look up the address, to avoid the
273-
# trap of hanging the entire event loop when the address
274-
# requires doing a DNS lookup. (OTOH, the caller should
275-
# already have done this, so it would be nice if we could
276-
# easily tell whether the address needs looking up or not. I
277-
# know how to do this for IPv4, but IPv6 addresses have many
278-
# syntaxes.)
279275
fd = sock.fileno()
280276
if registered:
281277
self.remove_writer(fd)

Lib/test/test_asyncio/test_events.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,18 @@ def wait():
11911191
{'clock_resolution': self.loop._clock_resolution,
11921192
'selector': self.loop._selector.__class__.__name__})
11931193

1194+
def test_sock_connect_address(self):
1195+
address = ('www.python.org', 80)
1196+
for family in (socket.AF_INET, socket.AF_INET6):
1197+
for sock_type in (socket.SOCK_STREAM, socket.SOCK_DGRAM):
1198+
sock = socket.socket(family, sock_type)
1199+
with sock:
1200+
connect = self.loop.sock_connect(sock, address)
1201+
with self.assertRaises(ValueError) as cm:
1202+
self.loop.run_until_complete(connect)
1203+
self.assertIn('address must be resolved',
1204+
str(cm.exception))
1205+
11941206

11951207
class SubprocessTestsMixin:
11961208

0 commit comments

Comments
 (0)