1212import asyncio
1313import builtins
1414import os
15+ import signal
1516import sys
1617import traceback
1718import types
@@ -158,27 +159,58 @@ def run(self) -> None:
158159 clear_title ()
159160 self ._remove_from_namespace ()
160161
161- async def run_and_show_expression_async (self , text : str ) -> object :
162- loop = asyncio .get_event_loop ()
162+ async def run_and_show_expression_async (self , text : str ) -> Any :
163+ loop = asyncio .get_running_loop ()
164+ system_exit : SystemExit | None = None
163165
164166 try :
165- result = await self .eval_async (text )
166- except KeyboardInterrupt : # KeyboardInterrupt doesn't inherit from Exception.
167- raise
168- except SystemExit :
169- return
170- except BaseException as e :
171- self ._handle_exception (e )
172- else :
173- # Print.
174- if result is not None :
175- await loop .run_in_executor (None , lambda : self ._show_result (result ))
167+ try :
168+ # Create `eval` task. Ensure that control-c will cancel this
169+ # task.
170+ async def eval () -> Any :
171+ nonlocal system_exit
172+ try :
173+ return await self .eval_async (text )
174+ except SystemExit as e :
175+ # Don't propagate SystemExit in `create_task()`. That
176+ # will kill the event loop. We want to handle it
177+ # gracefully.
178+ system_exit = e
179+
180+ task = asyncio .create_task (eval ())
181+ loop .add_signal_handler (signal .SIGINT , lambda * _ : task .cancel ())
182+ result = await task
183+
184+ if system_exit is not None :
185+ raise system_exit
186+ except KeyboardInterrupt :
187+ # KeyboardInterrupt doesn't inherit from Exception.
188+ raise
189+ except SystemExit :
190+ raise
191+ except BaseException as e :
192+ self ._handle_exception (e )
193+ else :
194+ # Print.
195+ if result is not None :
196+ await loop .run_in_executor (None , lambda : self ._show_result (result ))
176197
177- # Loop.
178- self .current_statement_index += 1
179- self .signatures = []
180- # Return the result for future consumers.
181- return result
198+ # Loop.
199+ self .current_statement_index += 1
200+ self .signatures = []
201+ # Return the result for future consumers.
202+ return result
203+ finally :
204+ loop .remove_signal_handler (signal .SIGINT )
205+
206+ except KeyboardInterrupt as e :
207+ # Handle all possible `KeyboardInterrupt` errors. This can
208+ # happen during the `eval`, but also during the
209+ # `show_result` if something takes too long.
210+ # (Try/catch is around the whole block, because we want to
211+ # prevent that a Control-C keypress terminates the REPL in
212+ # any case.)
213+ self ._handle_keyboard_interrupt (e )
182214
183215 async def run_async (self ) -> None :
184216 """
@@ -192,7 +224,7 @@ async def run_async(self) -> None:
192224 (Both for control-C to work, as well as for the code to see the right
193225 thread in which it was embedded).
194226 """
195- loop = asyncio .get_event_loop ()
227+ loop = asyncio .get_running_loop ()
196228
197229 if self .terminal_title :
198230 set_title (self .terminal_title )
@@ -222,6 +254,8 @@ async def run_async(self) -> None:
222254 # `KeyboardInterrupt` exceptions can end up in the event
223255 # loop selector.
224256 self ._handle_keyboard_interrupt (e )
257+ except SystemExit :
258+ return
225259 finally :
226260 if self .terminal_title :
227261 clear_title ()
@@ -250,7 +284,7 @@ def eval(self, line: str) -> object:
250284 result = eval (code , self .get_globals (), self .get_locals ())
251285
252286 if _has_coroutine_flag (code ):
253- result = asyncio .get_event_loop ().run_until_complete (result )
287+ result = asyncio .get_running_loop ().run_until_complete (result )
254288
255289 self ._store_eval_result (result )
256290 return result
@@ -263,7 +297,7 @@ def eval(self, line: str) -> object:
263297 result = eval (code , self .get_globals (), self .get_locals ())
264298
265299 if _has_coroutine_flag (code ):
266- result = asyncio .get_event_loop ().run_until_complete (result )
300+ result = asyncio .get_running_loop ().run_until_complete (result )
267301
268302 return None
269303
0 commit comments