Kaydet (Commit) 2fc23130 authored tarafından Victor Stinner's avatar Victor Stinner

asyncio: Only call _check_resolved_address() in debug mode

* _check_resolved_address() is implemented with getaddrinfo() which is slow
* If available, use socket.inet_pton() instead of socket.getaddrinfo(), because
  it is much faster

Microbenchmark (timeit) on Fedora 21 (Python 3.4, Linux 3.17, glibc 2.20) to
validate the IPV4 address "127.0.0.1" or the IPv6 address "::1":

* getaddrinfo() 10.4 usec per loop
* inet_pton(): 0.285 usec per loop

On glibc older than 2.14, getaddrinfo() always requests the list of all local
IP addresses to the kernel (using a NETLINK socket). getaddrinfo() has other
known issues, it's better to avoid it when it is possible.
üst aa41b9b2
...@@ -75,7 +75,11 @@ class _StopError(BaseException): ...@@ -75,7 +75,11 @@ class _StopError(BaseException):
def _check_resolved_address(sock, address): def _check_resolved_address(sock, address):
# Ensure that the address is already resolved to avoid the trap of hanging # Ensure that the address is already resolved to avoid the trap of hanging
# the entire event loop when the address requires doing a DNS lookup. # the entire event loop when the address requires doing a DNS lookup.
#
# getaddrinfo() is slow (around 10 us per call): this function should only
# be called in debug mode
family = sock.family family = sock.family
if family == socket.AF_INET: if family == socket.AF_INET:
host, port = address host, port = address
elif family == socket.AF_INET6: elif family == socket.AF_INET6:
...@@ -83,13 +87,24 @@ def _check_resolved_address(sock, address): ...@@ -83,13 +87,24 @@ def _check_resolved_address(sock, address):
else: else:
return return
# On Windows, socket.inet_pton() is only available since Python 3.4
if hasattr(socket, 'inet_pton'):
# getaddrinfo() is slow and has known issue: prefer inet_pton()
# if available
try:
socket.inet_pton(family, host)
except OSError as exc:
raise ValueError("address must be resolved (IP address), "
"got host %r: %s"
% (host, exc))
else:
# Use getaddrinfo(flags=AI_NUMERICHOST) to ensure that the address is
# already resolved.
type_mask = 0 type_mask = 0
if hasattr(socket, 'SOCK_NONBLOCK'): if hasattr(socket, 'SOCK_NONBLOCK'):
type_mask |= socket.SOCK_NONBLOCK type_mask |= socket.SOCK_NONBLOCK
if hasattr(socket, 'SOCK_CLOEXEC'): if hasattr(socket, 'SOCK_CLOEXEC'):
type_mask |= socket.SOCK_CLOEXEC type_mask |= socket.SOCK_CLOEXEC
# Use getaddrinfo(flags=AI_NUMERICHOST) to ensure that the address is
# already resolved.
try: try:
socket.getaddrinfo(host, port, socket.getaddrinfo(host, port,
family=family, family=family,
...@@ -97,8 +112,9 @@ def _check_resolved_address(sock, address): ...@@ -97,8 +112,9 @@ def _check_resolved_address(sock, address):
proto=sock.proto, proto=sock.proto,
flags=socket.AI_NUMERICHOST) flags=socket.AI_NUMERICHOST)
except socket.gaierror as err: except socket.gaierror as err:
raise ValueError("address must be resolved (IP address), got %r: %s" raise ValueError("address must be resolved (IP address), "
% (address, err)) "got host %r: %s"
% (host, err))
def _raise_stop_error(*args): def _raise_stop_error(*args):
raise _StopError raise _StopError
......
...@@ -437,6 +437,7 @@ class BaseProactorEventLoop(base_events.BaseEventLoop): ...@@ -437,6 +437,7 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
def sock_connect(self, sock, address): def sock_connect(self, sock, address):
try: try:
if self._debug:
base_events._check_resolved_address(sock, address) base_events._check_resolved_address(sock, address)
except ValueError as err: except ValueError as err:
fut = futures.Future(loop=self) fut = futures.Future(loop=self)
......
...@@ -397,6 +397,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): ...@@ -397,6 +397,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
fut = futures.Future(loop=self) fut = futures.Future(loop=self)
try: try:
if self._debug:
base_events._check_resolved_address(sock, address) base_events._check_resolved_address(sock, address)
except ValueError as err: except ValueError as err:
fut.set_exception(err) fut.set_exception(err)
......
...@@ -1437,6 +1437,10 @@ class EventLoopTestsMixin: ...@@ -1437,6 +1437,10 @@ class EventLoopTestsMixin:
'selector': self.loop._selector.__class__.__name__}) 'selector': self.loop._selector.__class__.__name__})
def test_sock_connect_address(self): def test_sock_connect_address(self):
# In debug mode, sock_connect() must ensure that the address is already
# resolved (call _check_resolved_address())
self.loop.set_debug(True)
addresses = [(socket.AF_INET, ('www.python.org', 80))] addresses = [(socket.AF_INET, ('www.python.org', 80))]
if support.IPV6_ENABLED: if support.IPV6_ENABLED:
addresses.extend(( addresses.extend((
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment