Skip to content

Commit

Permalink
bpo-32751: Wait for task cancel in asyncio.wait_for() when timeout <=…
Browse files Browse the repository at this point in the history
… 0 (GH-21895) (GH-21963)

When I was fixing bpo-32751 back in GH-7216 I missed the case when
*timeout* is zero or negative.  This takes care of that.

Props to @aaliddell for noticing the inconsistency.
(cherry picked from commit c517fc7)

Co-authored-by: Elvis Pranskevichus <elvis@magic.io>
  • Loading branch information
miss-islington and elprans authored Aug 26, 2020
1 parent d7cd116 commit 1036ccb
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 2 deletions.
9 changes: 7 additions & 2 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,13 @@ async def wait_for(fut, timeout, *, loop=None):
if fut.done():
return fut.result()

fut.cancel()
raise exceptions.TimeoutError()
await _cancel_and_wait(fut, loop=loop)
try:
fut.result()
except exceptions.CancelledError as exc:
raise exceptions.TimeoutError() from exc
else:
raise exceptions.TimeoutError()

waiter = loop.create_future()
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
Expand Down
31 changes: 31 additions & 0 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,9 @@ async def inner():
nonlocal task_done
try:
await asyncio.sleep(0.2)
except asyncio.CancelledError:
await asyncio.sleep(_EPSILON)
raise
finally:
task_done = True

Expand All @@ -1145,6 +1148,34 @@ async def inner():
chained = cm.exception.__context__
self.assertEqual(type(chained), asyncio.CancelledError)

def test_wait_for_waits_for_task_cancellation_w_timeout_0(self):
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)

task_done = False

async def foo():
async def inner():
nonlocal task_done
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
await asyncio.sleep(_EPSILON)
raise
finally:
task_done = True

inner_task = self.new_task(loop, inner())
await asyncio.sleep(_EPSILON)
await asyncio.wait_for(inner_task, timeout=0)

with self.assertRaises(asyncio.TimeoutError) as cm:
loop.run_until_complete(foo())

self.assertTrue(task_done)
chained = cm.exception.__context__
self.assertEqual(type(chained), asyncio.CancelledError)

def test_wait_for_reraises_exception_during_cancellation(self):
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
When cancelling the task due to a timeout, :meth:`asyncio.wait_for` will now
wait until the cancellation is complete also in the case when *timeout* is
<= 0, like it does with positive timeouts.

0 comments on commit 1036ccb

Please sign in to comment.