diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index d078ebfa4cedbe..bafd95f26e5b9a 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -2,21 +2,17 @@ import ast import asyncio import asyncio.tools -import concurrent.futures import contextvars import inspect import os import site import sys -import threading import types import warnings from _colorize import get_theme from _pyrepl.console import InteractiveColoredConsole -from . import futures - class AsyncIOInteractiveConsole(InteractiveColoredConsole): @@ -29,122 +25,83 @@ def __init__(self, locals, loop): def runcode(self, code): global return_code - future = concurrent.futures.Future() - - def callback(): - global return_code - global repl_future - global keyboard_interrupted - - repl_future = None - keyboard_interrupted = False + async def callback(): func = types.FunctionType(code, self.locals) - try: - coro = func() - except SystemExit as se: - return_code = se.code - self.loop.stop() - return - except KeyboardInterrupt as ex: - keyboard_interrupted = True - future.set_exception(ex) - return - except BaseException as ex: - future.set_exception(ex) - return + coro = func() if not inspect.iscoroutine(coro): - future.set_result(coro) - return - - try: - repl_future = self.loop.create_task(coro, context=self.context) - futures._chain_future(repl_future, future) - except BaseException as exc: - future.set_exception(exc) + return coro + return await coro - self.loop.call_soon_threadsafe(callback, context=self.context) + task = self.loop.create_task(callback(), context=self.context) try: - return future.result() - except SystemExit as se: - return_code = se.code - self.loop.stop() - return + return self.loop.run_until_complete(task) + except SystemExit: + raise except BaseException: - if keyboard_interrupted: - if not CAN_USE_PYREPL: - self.write("\nKeyboardInterrupt\n") - else: - self.showtraceback() + self.showtraceback() return self.STATEMENT_FAILED -class REPLThread(threading.Thread): - def run(self): - global return_code +def interact(): + global return_code - try: - banner = ( - f'asyncio REPL {sys.version} on {sys.platform}\n' - f'Use "await" directly instead of "asyncio.run()".\n' - f'Type "help", "copyright", "credits" or "license" ' - f'for more information.\n' - ) + try: + banner = ( + f'asyncio REPL {sys.version} on {sys.platform}\n' + f'Use "await" directly instead of "asyncio.run()".\n' + f'Type "help", "copyright", "credits" or "license" ' + f'for more information.\n' + ) + + console.write(banner) + + if startup_path := os.getenv("PYTHONSTARTUP"): + sys.audit("cpython.run_startup", startup_path) + + import tokenize + with tokenize.open(startup_path) as f: + startup_code = compile(f.read(), startup_path, "exec") + exec(startup_code, console.locals) + + ps1 = getattr(sys, "ps1", ">>> ") + if CAN_USE_PYREPL: + theme = get_theme().syntax + ps1 = f"{theme.prompt}{ps1}{theme.reset}" + import_line = f'{theme.keyword}import{theme.reset} asyncio' + else: + import_line = "import asyncio" + console.write(f"{ps1}{import_line}\n") - console.write(banner) - - if startup_path := os.getenv("PYTHONSTARTUP"): - sys.audit("cpython.run_startup", startup_path) - - import tokenize - with tokenize.open(startup_path) as f: - startup_code = compile(f.read(), startup_path, "exec") - exec(startup_code, console.locals) - - ps1 = getattr(sys, "ps1", ">>> ") - if CAN_USE_PYREPL: - theme = get_theme().syntax - ps1 = f"{theme.prompt}{ps1}{theme.reset}" - import_line = f'{theme.keyword}import{theme.reset} asyncio' - else: - import_line = "import asyncio" - console.write(f"{ps1}{import_line}\n") - - if CAN_USE_PYREPL: - from _pyrepl.simple_interact import ( - run_multiline_interactive_console, - ) - try: - sys.ps1 = ps1 - run_multiline_interactive_console(console) - except SystemExit: - # expected via the `exit` and `quit` commands - pass - except BaseException: - # unexpected issue - console.showtraceback() - console.write("Internal error, ") - return_code = 1 - else: + if CAN_USE_PYREPL: + from _pyrepl.simple_interact import ( + run_multiline_interactive_console, + ) + try: + sys.ps1 = ps1 + run_multiline_interactive_console(console) + except SystemExit as se: + # expected via the `exit` and `quit` commands + return_code = se.code + except BaseException: + # unexpected issue + console.showtraceback() + console.write("Internal error, ") + return_code = 1 + else: + try: console.interact(banner="", exitmsg="") - finally: - warnings.filterwarnings( - 'ignore', - message=r'^coroutine .* was never awaited$', - category=RuntimeWarning) - - loop.call_soon_threadsafe(loop.stop) - - def interrupt(self) -> None: - if not CAN_USE_PYREPL: - return + except SystemExit as se: + return_code = se.code + finally: + warnings.filterwarnings( + 'ignore', + message=r'^coroutine .* was never awaited$', + category=RuntimeWarning) - from _pyrepl.simple_interact import _get_reader - r = _get_reader() - if r.threading_hook is not None: - r.threading_hook.add("") # type: ignore + loop.call_soon_threadsafe(loop.stop) if __name__ == '__main__': @@ -198,9 +155,6 @@ def interrupt(self) -> None: console = AsyncIOInteractiveConsole(repl_locals, loop) - repl_future = None - keyboard_interrupted = False - try: import readline # NoQA except ImportError: @@ -223,21 +177,6 @@ def interrupt(self) -> None: completer = rlcompleter.Completer(console.locals) readline.set_completer(completer.complete) - repl_thread = REPLThread(name="Interactive thread") - repl_thread.daemon = True - repl_thread.start() - - while True: - try: - loop.run_forever() - except KeyboardInterrupt: - keyboard_interrupted = True - if repl_future and not repl_future.done(): - repl_future.cancel() - repl_thread.interrupt() - continue - else: - break - + interact() console.write('exiting asyncio REPL...\n') sys.exit(return_code) diff --git a/Misc/NEWS.d/next/Library/2025-10-20-09-03-46.gh-issue-140355.fSj0zi.rst b/Misc/NEWS.d/next/Library/2025-10-20-09-03-46.gh-issue-140355.fSj0zi.rst new file mode 100644 index 00000000000000..4c4f917af63998 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-20-09-03-46.gh-issue-140355.fSj0zi.rst @@ -0,0 +1,2 @@ +Refactor and simplify the asyncio REPL by removing the threading and future handling. +This fixes an issue on MacOS that the readline module will be broken after pressing Ctrl+C.