Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 20 additions & 67 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,65 +99,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 @@ -938,6 +879,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 @@ -1514,14 +1464,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 @@ -2059,31 +2060,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 @@ -172,7 +172,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