From aa41b9b22b139d241dd97f8baf0fda56fe719c36 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 4 Feb 2015 14:50:59 +0100 Subject: [PATCH 1/2] asyncio: BaseSelectorEventLoop uses directly the private _debug attribute Just try to be consistent: _debug was already used in some places, and always used in BaseProactorEventLoop. --- Lib/asyncio/selector_events.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 4bd6dc8d1cb..7cbd4fd108d 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -214,7 +214,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): # It's now up to the protocol to handle the connection. except Exception as exc: - if self.get_debug(): + if self._debug: context = { 'message': ('Error on transport creation ' 'for incoming connection'), @@ -312,7 +312,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): This method is a coroutine. """ - if self.get_debug() and sock.gettimeout() != 0: + if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") fut = futures.Future(loop=self) self._sock_recv(fut, False, sock, n) @@ -350,7 +350,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): This method is a coroutine. """ - if self.get_debug() and sock.gettimeout() != 0: + if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") fut = futures.Future(loop=self) if data: @@ -393,7 +393,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): This method is a coroutine. """ - if self.get_debug() and sock.gettimeout() != 0: + if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") fut = futures.Future(loop=self) try: @@ -453,7 +453,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): This method is a coroutine. """ - if self.get_debug() and sock.gettimeout() != 0: + if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") fut = futures.Future(loop=self) self._sock_accept(fut, False, sock) From 2fc2313038cb2ef0f375808244783235b8ad6455 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 4 Feb 2015 14:51:23 +0100 Subject: [PATCH 2/2] 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. --- Lib/asyncio/base_events.py | 48 ++++++++++++++++++---------- Lib/asyncio/proactor_events.py | 3 +- Lib/asyncio/selector_events.py | 3 +- Lib/test/test_asyncio/test_events.py | 4 +++ 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 7108f2516ad..5c3975433b4 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -75,7 +75,11 @@ class _StopError(BaseException): def _check_resolved_address(sock, address): # 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. + # + # getaddrinfo() is slow (around 10 us per call): this function should only + # be called in debug mode family = sock.family + if family == socket.AF_INET: host, port = address elif family == socket.AF_INET6: @@ -83,22 +87,34 @@ def _check_resolved_address(sock, address): else: return - type_mask = 0 - if hasattr(socket, 'SOCK_NONBLOCK'): - type_mask |= socket.SOCK_NONBLOCK - if hasattr(socket, 'SOCK_CLOEXEC'): - type_mask |= socket.SOCK_CLOEXEC - # Use getaddrinfo(flags=AI_NUMERICHOST) to ensure that the address is - # already resolved. - try: - socket.getaddrinfo(host, port, - family=family, - type=(sock.type & ~type_mask), - proto=sock.proto, - flags=socket.AI_NUMERICHOST) - except socket.gaierror as err: - raise ValueError("address must be resolved (IP address), got %r: %s" - % (address, err)) + # 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 + if hasattr(socket, 'SOCK_NONBLOCK'): + type_mask |= socket.SOCK_NONBLOCK + if hasattr(socket, 'SOCK_CLOEXEC'): + type_mask |= socket.SOCK_CLOEXEC + try: + socket.getaddrinfo(host, port, + family=family, + type=(sock.type & ~type_mask), + proto=sock.proto, + flags=socket.AI_NUMERICHOST) + except socket.gaierror as err: + raise ValueError("address must be resolved (IP address), " + "got host %r: %s" + % (host, err)) def _raise_stop_error(*args): raise _StopError diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 65de926be8e..9c2b8f155a7 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -437,7 +437,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop): def sock_connect(self, sock, address): try: - base_events._check_resolved_address(sock, address) + if self._debug: + base_events._check_resolved_address(sock, address) except ValueError as err: fut = futures.Future(loop=self) fut.set_exception(err) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 7cbd4fd108d..a38ed1cee3b 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -397,7 +397,8 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): raise ValueError("the socket must be non-blocking") fut = futures.Future(loop=self) try: - base_events._check_resolved_address(sock, address) + if self._debug: + base_events._check_resolved_address(sock, address) except ValueError as err: fut.set_exception(err) else: diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 4b957d8f636..8fbba8fe0fc 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1437,6 +1437,10 @@ class EventLoopTestsMixin: 'selector': self.loop._selector.__class__.__name__}) 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))] if support.IPV6_ENABLED: addresses.extend((