Don't stop the loop in `run_until_complete()` on `SystemExit` and `KeyboardInterrupt` exceptions (#337)

* Don't stop the loop in run_until_complete on SystemExit & KeyboardInterrupt
* Add unit test (based on CPython's equivalent)
* Fix the test to resume its coverage.

Co-authored-by: Victor Stinner <victor.stinner@gmail.com>
Co-authored-by: Fantix King <fantix.king@gmail.com>
This commit is contained in:
jack1142 2021-02-09 00:25:53 +01:00 committed by GitHub
parent b7048b94a2
commit 8c471f827b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 1 deletions

View File

@ -361,6 +361,26 @@ class _TestBase:
# done-callback for the previous future. # done-callback for the previous future.
self.loop.run_until_complete(foo(0.2)) self.loop.run_until_complete(foo(0.2))
def test_run_until_complete_keyboard_interrupt(self):
# Issue #336: run_until_complete() must not schedule a pending
# call to stop() if the future raised a KeyboardInterrupt
async def raise_keyboard_interrupt():
raise KeyboardInterrupt
self.loop._process_events = mock.Mock()
with self.assertRaises(KeyboardInterrupt):
self.loop.run_until_complete(raise_keyboard_interrupt())
def func():
self.loop.stop()
func.called = True
func.called = False
self.loop.call_later(0.01, func)
self.loop.run_forever()
self.assertTrue(func.called)
def test_debug_slow_callbacks(self): def test_debug_slow_callbacks(self):
logger = logging.getLogger('asyncio') logger = logging.getLogger('asyncio')
self.loop.set_debug(True) self.loop.set_debug(True)

View File

@ -1431,7 +1431,14 @@ cdef class Loop:
# is no need to log the "destroy pending task" message # is no need to log the "destroy pending task" message
future._log_destroy_pending = False future._log_destroy_pending = False
done_cb = lambda fut: self.stop() def done_cb(fut):
if not fut.cancelled():
exc = fut.exception()
if isinstance(exc, (SystemExit, KeyboardInterrupt)):
# Issue #336: run_forever() already finished,
# no need to stop it.
return
self.stop()
future.add_done_callback(done_cb) future.add_done_callback(done_cb)
try: try: