]> granicus.if.org Git - python/commitdiff
asyncio: Only call _check_resolved_address() in debug mode
authorVictor Stinner <victor.stinner@gmail.com>
Wed, 4 Feb 2015 13:51:23 +0000 (14:51 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Wed, 4 Feb 2015 13:51:23 +0000 (14:51 +0100)
* _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
Lib/asyncio/proactor_events.py
Lib/asyncio/selector_events.py
Lib/test/test_asyncio/test_events.py

index 7108f2516ad649500acc4f98123db17df143d4f6..5c3975433b4957669d7a98a5f6bdcbe56ea4138b 100644 (file)
@@ -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
index 65de926be8e439d0b6d77a642bf1560a63739795..9c2b8f155a73ac1ca2f88e68e93d4b076635f04a 100644 (file)
@@ -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)
index 7cbd4fd108d3056fc0e961c1088bc0cc3c2f0154..a38ed1cee3ba1fec73411e101412019e6b212cd0 100644 (file)
@@ -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:
index 4b957d8f636b87390b5f1659ca8655a748d5b18d..8fbba8fe0fc571f5f075c95e6f56eb62837e9be8 100644 (file)
@@ -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((