Skip to content
87 changes: 20 additions & 67 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,65 +100,6 @@ def _set_reuseport(sock):
'SO_REUSEPORT defined but not implemented.')


def _ipaddr_info(host, port, family, type, proto, flowinfo=0, scopeid=0):
# Try to skip getaddrinfo if "host" is already an IP. Users might have
# handled name resolution in their own code and pass in resolved IPs.
if not hasattr(socket, 'inet_pton'):
return

if proto not in {0, socket.IPPROTO_TCP, socket.IPPROTO_UDP} or \
host is None:
return None

if type == socket.SOCK_STREAM:
proto = socket.IPPROTO_TCP
elif type == socket.SOCK_DGRAM:
proto = socket.IPPROTO_UDP
else:
return None

if port is None:
port = 0
elif isinstance(port, bytes) and port == b'':
port = 0
elif isinstance(port, str) and port == '':
port = 0
else:
# If port's a service name like "http", don't skip getaddrinfo.
try:
port = int(port)
except (TypeError, ValueError):
return None

if family == socket.AF_UNSPEC:
afs = [socket.AF_INET]
if _HAS_IPv6:
afs.append(socket.AF_INET6)
else:
afs = [family]

if isinstance(host, bytes):
host = host.decode('idna')
if '%' in host:
# Linux's inet_pton doesn't accept an IPv6 zone index after host,
# like '::1%lo0'.
return None

for af in afs:
try:
socket.inet_pton(af, host)
# The host has already been resolved.
if _HAS_IPv6 and af == socket.AF_INET6:
return af, type, proto, '', (host, port, flowinfo, scopeid)
else:
return af, type, proto, '', (host, port)
except OSError:
pass

# "host" is not an IP address.
return None


def _interleave_addrinfos(addrinfos, first_address_family_count=1):
"""Interleave list of addrinfo tuples by family."""
# Group addresses by family
Expand Down Expand Up @@ -939,6 +880,15 @@ async def getaddrinfo(self, host, port, *,
else:
getaddr_func = socket.getaddrinfo

try:
return getaddr_func(
host, port, family, type, proto,
flags | socket.AI_NUMERICHOST | socket.AI_NUMERICSERV,
)
except socket.gaierror as e:
if e.errno != socket.EAI_NONAME:
raise

return await self.run_in_executor(
None, getaddr_func, host, port, family, type, proto, flags)

Expand Down Expand Up @@ -1522,14 +1472,17 @@ async def create_datagram_endpoint(self, protocol_factory,
async def _ensure_resolved(self, address, *,
family=0, type=socket.SOCK_STREAM,
proto=0, flags=0, loop):
host, port = address[:2]
info = _ipaddr_info(host, port, family, type, proto, *address[2:])
if info is not None:
# "host" is already a resolved IP.
return [info]
else:
return await loop.getaddrinfo(host, port, family=family, type=type,
proto=proto, flags=flags)
host, port, *rest = address
if not rest:
return await loop.getaddrinfo(
host, port, family=family, type=type, proto=proto, flags=flags,
)
return [
(*first, (host, port, *rest)) for *first, (host, port, *_) in
await loop.getaddrinfo(
host, port, family=family, type=type, proto=proto, flags=flags,
)
]

async def _create_server_getaddrinfo(self, host, port, family, flags):
infos = await self._ensure_resolved((host, port), family=family,
Expand Down
28 changes: 2 additions & 26 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def mock_socket_module():
m_socket = mock.MagicMock(spec=socket)
for name in (
'AF_INET', 'AF_INET6', 'AF_UNSPEC', 'IPPROTO_TCP', 'IPPROTO_UDP',
'SOCK_STREAM', 'SOCK_DGRAM', 'SOL_SOCKET', 'SO_REUSEADDR', 'inet_pton'
'SOCK_STREAM', 'SOCK_DGRAM', 'SOL_SOCKET', 'SO_REUSEADDR', 'inet_pton',
'gaierror', 'AI_NUMERICHOST', 'AI_NUMERICSERV', 'EAI_NONAME',
):
if hasattr(socket, name):
setattr(m_socket, name, getattr(socket, name))
Expand Down Expand Up @@ -2159,31 +2160,6 @@ def test_create_datagram_endpoint_nosoreuseport(self, m_socket):

self.assertRaises(ValueError, self.loop.run_until_complete, coro)

@patch_socket
def test_create_datagram_endpoint_ip_addr(self, m_socket):
def getaddrinfo(*args, **kw):
self.fail('should not have called getaddrinfo')

m_socket.getaddrinfo = getaddrinfo
m_socket.socket.return_value.bind = bind = mock.Mock()
self.loop._add_reader = mock.Mock()

reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
coro = self.loop.create_datagram_endpoint(
lambda: MyDatagramProto(loop=self.loop),
local_addr=('1.2.3.4', 0),
reuse_port=reuseport_supported)

t, p = self.loop.run_until_complete(coro)
try:
bind.assert_called_with(('1.2.3.4', 0))
m_socket.socket.assert_called_with(family=m_socket.AF_INET,
proto=m_socket.IPPROTO_UDP,
type=m_socket.SOCK_DGRAM)
finally:
t.close()
test_utils.run_briefly(self.loop) # allow transport to close

def test_accept_connection_retry(self):
sock = mock.Mock()
sock.accept.side_effect = BlockingIOError()
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_asyncio/test_selector_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ def test_sock_connect_resolve_using_socket_params(self, m_gai):
con = self.loop.create_task(self.loop.sock_connect(sock, addr))
self.loop.run_until_complete(con)
m_gai.assert_called_with(
addr[0], addr[1], sock.family, sock.type, sock.proto, 0)
addr[0], addr[1], sock.family, sock.type, sock.proto,
mock.ANY,
)

self.loop.run_until_complete(con)
sock.connect.assert_called_with(('127.0.0.1', 0))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
skip getaddrinfo thread if host is already resolved, using socket.AI_NUMERIC
Loading