Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a9acbe8

Browse files
committed
Closes #21886, #21447: Fix a race condition in asyncio when setting the result
of a Future with call_soon(). Add an helper, a private method, to set the result only if the future was not cancelled.
1 parent 5021cb5 commit a9acbe8

9 files changed

Lines changed: 31 additions & 7 deletions

File tree

Lib/asyncio/coroutines.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ def __init__(self, gen, func):
6464
self.gen = gen
6565
self.func = func
6666
self._source_traceback = traceback.extract_stack(sys._getframe(1))
67+
# __name__, __qualname__, __doc__ attributes are set by the coroutine()
68+
# decorator
69+
70+
def __repr__(self):
71+
return ('<%s %s>'
72+
% (self.__class__.__name__, _format_coroutine(self)))
6773

6874
def __iter__(self):
6975
return self

Lib/asyncio/futures.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,12 @@ def remove_done_callback(self, fn):
316316

317317
# So-called internal methods (note: no set_running_or_notify_cancel()).
318318

319+
def _set_result_unless_cancelled(self, result):
320+
"""Helper setting the result only if the future was not cancelled."""
321+
if self.cancelled():
322+
return
323+
self.set_result(result)
324+
319325
def set_result(self, result):
320326
"""Mark the future done and set its result.
321327

Lib/asyncio/proactor_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __init__(self, loop, sock, protocol, waiter=None,
3838
self._server.attach(self)
3939
self._loop.call_soon(self._protocol.connection_made, self)
4040
if waiter is not None:
41-
self._loop.call_soon(waiter.set_result, None)
41+
self._loop.call_soon(waiter._set_result_unless_cancelled, None)
4242

4343
def _set_extra(self, sock):
4444
self._extra['pipe'] = sock

Lib/asyncio/queues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def get(self):
173173
# run, we need to defer the put for a tick to ensure that
174174
# getters and putters alternate perfectly. See
175175
# ChannelTest.test_wait.
176-
self._loop.call_soon(putter.set_result, None)
176+
self._loop.call_soon(putter._set_result_unless_cancelled, None)
177177

178178
return self._get()
179179

Lib/asyncio/selector_events.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ def __init__(self, loop, sock, protocol, waiter=None,
481481
self._loop.add_reader(self._sock_fd, self._read_ready)
482482
self._loop.call_soon(self._protocol.connection_made, self)
483483
if waiter is not None:
484-
self._loop.call_soon(waiter.set_result, None)
484+
self._loop.call_soon(waiter._set_result_unless_cancelled, None)
485485

486486
def pause_reading(self):
487487
if self._closing:
@@ -690,7 +690,8 @@ def _on_handshake(self):
690690
self._loop.add_reader(self._sock_fd, self._read_ready)
691691
self._loop.call_soon(self._protocol.connection_made, self)
692692
if self._waiter is not None:
693-
self._loop.call_soon(self._waiter.set_result, None)
693+
self._loop.call_soon(self._waiter._set_result_unless_cancelled,
694+
None)
694695

695696
def pause_reading(self):
696697
# XXX This is a bit icky, given the comment at the top of

Lib/asyncio/tasks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,8 @@ def _wait_for_one():
487487
def sleep(delay, result=None, *, loop=None):
488488
"""Coroutine that completes after a given time (in seconds)."""
489489
future = futures.Future(loop=loop)
490-
h = future._loop.call_later(delay, future.set_result, result)
490+
h = future._loop.call_later(delay,
491+
future._set_result_unless_cancelled, result)
491492
try:
492493
return (yield from future)
493494
finally:

Lib/asyncio/unix_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
269269
self._loop.add_reader(self._fileno, self._read_ready)
270270
self._loop.call_soon(self._protocol.connection_made, self)
271271
if waiter is not None:
272-
self._loop.call_soon(waiter.set_result, None)
272+
self._loop.call_soon(waiter._set_result_unless_cancelled, None)
273273

274274
def _read_ready(self):
275275
try:
@@ -353,7 +353,7 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
353353

354354
self._loop.call_soon(self._protocol.connection_made, self)
355355
if waiter is not None:
356-
self._loop.call_soon(waiter.set_result, None)
356+
self._loop.call_soon(waiter._set_result_unless_cancelled, None)
357357

358358
def get_write_buffer_size(self):
359359
return sum(len(data) for data in self._buffer)

Lib/test/test_asyncio/test_futures.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,12 @@ def memroy_error():
343343
message = m_log.error.call_args[0][0]
344344
self.assertRegex(message, re.compile(regex, re.DOTALL))
345345

346+
def test_set_result_unless_cancelled(self):
347+
fut = asyncio.Future(loop=self.loop)
348+
fut.cancel()
349+
fut._set_result_unless_cancelled(2)
350+
self.assertTrue(fut.cancelled())
351+
346352

347353
class FutureDoneCallbackTests(test_utils.TestCase):
348354

Lib/test/test_asyncio/test_tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ def notmuch():
211211
coro = ('%s() at %s:%s'
212212
% (coro_qualname, code.co_filename, code.co_firstlineno))
213213

214+
# test repr(CoroWrapper)
215+
if coroutines._DEBUG:
216+
self.assertEqual(repr(gen), '<CoroWrapper %s>' % coro)
217+
214218
# test pending Task
215219
t = asyncio.Task(gen, loop=self.loop)
216220
t.add_done_callback(Dummy())

0 commit comments

Comments
 (0)