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
42 changes: 42 additions & 0 deletions tests/test_dns.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import gc
import socket
import unittest

Expand Down Expand Up @@ -238,6 +239,47 @@ def test_getnameinfo_4(self):
def test_getnameinfo_5(self):
self._test_getnameinfo(('localhost', 8080), 0)

def test_create_connection_cancel_during_getaddrinfo(self):
# Regression test for https://github.com/MagicStack/uvloop/pull/590
#
# When create_connection() is cancelled (e.g. by a wait_for()
# timeout) while its DNS lookup is still in flight, the lookup
# must be cancelled as well. Otherwise, once the lookup fails,
# nobody retrieves its exception and the loop logs
# "Future exception was never retrieved".

host = 'no-such-host.invalid' # reserved TLD, see RFC 2606

messages = []
self.loop.set_exception_handler(
lambda loop, ctx: messages.append(ctx))

async def run():
task = self.loop.create_task(
self.loop.create_connection(asyncio.Protocol, host, 80))
# Let create_connection() start the DNS lookup, then cancel
# it before the lookup result is delivered to the loop.
await asyncio.sleep(0)
task.cancel()
with self.assertRaises(asyncio.CancelledError):
await task
del task

# Wait for the lookup to fail. The resolver answers requests
# roughly in order, so a second lookup of the same host is a
# good proxy for the first one being done.
try:
await self.loop.getaddrinfo(host, 80)
except socket.gaierror:
pass
else:
raise unittest.SkipTest(f'{host!r} unexpectedly resolves')
await asyncio.sleep(0.1)
gc.collect()

self.loop.run_until_complete(run())
self.assertEqual(messages, [])


class Test_UV_DNS(BaseTestDNS, tb.UVTestCase):

Expand Down
14 changes: 13 additions & 1 deletion uvloop/loop.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1972,7 +1972,19 @@ cdef class Loop:
lai = &lai_static

if len(fs):
await aio_wait(fs)
try:
await aio_wait(fs)
except asyncio.CancelledError:
# The caller gave up (e.g. a wait_for() timeout) while
# the DNS lookups were still in progress. Cancel them
# so that a lookup failing later doesn't get reported
# as "Future exception was never retrieved". A lookup
# that has already finished can't be cancelled, so mark
# its exception (if any) as retrieved instead.
for fut in fs:
if not fut.cancel() and not fut.cancelled():
fut.exception()
raise

if rai is NULL:
ai_remote = f1.result()
Expand Down
Loading