ipython
ipython copied to clipboard
KeyboardInterrupt on awaited async function does not stop it completely
This seam to be related to https://github.com/ipython/ipython/issues/13737 but in a different nesting order of blocking/async code.
When running an async function with await directly in ipython and calling a KeybordInterrupt it does not completely stop it (it seams that the keyboard interrupt is sent to the currently running function but not to all levels). When executing the async function with asyncio.run()
it stops it correctly.
Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.17.2 -- An enhanced Interactive Python. Type '?' for help.
In [1]: %gui asyncio
Installed asyncio event loop hook.
In [2]: import asyncio
In [3]: async def run_func():
...: for i in range(20):
...: print("foo")
...: await asyncio.sleep(2)
...:
In [4]: await run_func()
foo
foo
^C---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
File ~/.local/lib/python3.10/site-packages/IPython/core/async_helpers.py:56, in _AsyncIORunner.__call__(self, coro)
52 def __call__(self, coro):
53 """
54 Handler for asyncio autoawait
55 """
---> 56 return get_asyncio_loop().run_until_complete(coro)
File /usr/lib/python3.10/asyncio/base_events.py:636, in BaseEventLoop.run_until_complete(self, future)
634 future.add_done_callback(_run_until_complete_cb)
635 try:
--> 636 self.run_forever()
637 except:
638 if new_task and future.done() and not future.cancelled():
639 # The coroutine raised a BaseException. Consume the exception
640 # to not log a warning, the caller doesn't have access to the
641 # local task.
File /usr/lib/python3.10/asyncio/base_events.py:603, in BaseEventLoop.run_forever(self)
601 events._set_running_loop(self)
602 while True:
--> 603 self._run_once()
604 if self._stopping:
605 break
File /usr/lib/python3.10/asyncio/base_events.py:1871, in BaseEventLoop._run_once(self)
1868 when = self._scheduled[0]._when
1869 timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT)
-> 1871 event_list = self._selector.select(timeout)
1872 self._process_events(event_list)
1873 # Needed to break cycles when an exception occurs.
File /usr/lib/python3.10/selectors.py:469, in EpollSelector.select(self, timeout)
467 ready = []
468 try:
--> 469 fd_event_list = self._selector.poll(timeout, max_ev)
470 except InterruptedError:
471 return ready
KeyboardInterrupt:
foo # From here I have my prompt back but the loop is still running in the background
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo # Now the loop finished
In [4]: asyncio.run(run_func())
foo
foo
^C---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[5], line 1
----> 1 asyncio.run(run_func())
File /usr/lib/python3.10/asyncio/runners.py:44, in run(main, debug)
42 if debug is not None:
43 loop.set_debug(debug)
---> 44 return loop.run_until_complete(main)
45 finally:
46 try:
File /usr/lib/python3.10/asyncio/base_events.py:636, in BaseEventLoop.run_until_complete(self, future)
634 future.add_done_callback(_run_until_complete_cb)
635 try:
--> 636 self.run_forever()
637 except:
638 if new_task and future.done() and not future.cancelled():
639 # The coroutine raised a BaseException. Consume the exception
640 # to not log a warning, the caller doesn't have access to the
641 # local task.
File /usr/lib/python3.10/asyncio/base_events.py:603, in BaseEventLoop.run_forever(self)
601 events._set_running_loop(self)
602 while True:
--> 603 self._run_once()
604 if self._stopping:
605 break
File /usr/lib/python3.10/asyncio/base_events.py:1871, in BaseEventLoop._run_once(self)
1868 when = self._scheduled[0]._when
1869 timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT)
-> 1871 event_list = self._selector.select(timeout)
1872 self._process_events(event_list)
1873 # Needed to break cycles when an exception occurs.
File /usr/lib/python3.10/selectors.py:469, in EpollSelector.select(self, timeout)
467 ready = []
468 try:
--> 469 fd_event_list = self._selector.poll(timeout, max_ev)
470 except InterruptedError:
471 return ready
KeyboardInterrupt:
In [6]: # No more printings
Present in:
python3 -c "import IPython; print(IPython.sys_info())"
{'commit_hash': 'ef3bae307',
'commit_source': 'installation',
'default_encoding': 'utf-8',
'ipython_path': '/home/username/.local/lib/python3.10/site-packages/IPython',
'ipython_version': '8.17.2',
'os_name': 'posix',
'platform': 'Linux-6.2.0-36-generic-x86_64-with-glibc2.35',
'sys_executable': '/usr/bin/python3',
'sys_platform': 'linux',
'sys_version': '3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]'}
{'commit_hash': 'b5cd02544',
'commit_source': 'installation',
'default_encoding': 'utf-8',
'ipython_path': '/usr/local/lib/python3.8/dist-packages/IPython',
'ipython_version': '8.2.0',
'os_name': 'posix',
'platform': 'Linux-5.4.0-164-generic-x86_64-with-glibc2.29',
'sys_executable': '/usr/bin/python3',
'sys_platform': 'linux',
'sys_version': '3.8.10 (default, May 26 2023, 14:05:08) \n[GCC 9.4.0]'}