diff options
| author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2024-09-06 20:25:19 (GMT) |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-06 20:25:19 (GMT) |
| commit | 5c3078d6e597c7e50b3b0da37f493e2dfca17a6a (patch) | |
| tree | c8f16cbf540fee5b7527d51b3b9ff7ed9c8b065e /Lib/_pyrepl/_threading_handler.py | |
| parent | 66b15381f187f00f0fd91575f9f11e14bfddeeca (diff) | |
| download | cpython-5c3078d6e597c7e50b3b0da37f493e2dfca17a6a.zip cpython-5c3078d6e597c7e50b3b0da37f493e2dfca17a6a.tar.gz cpython-5c3078d6e597c7e50b3b0da37f493e2dfca17a6a.tar.bz2 | |
[3.13] gh-120221: Support KeyboardInterrupt in asyncio REPL (GH-123795) (#123799)
This switches the main pyrepl event loop to always be non-blocking so that it
can listen to incoming interruptions from other threads.
This also resolves invalid display of exceptions from other threads
(gh-123178).
This also fixes freezes with pasting and an active input hook.
(cherry picked from commit 033510e11dff742d9626b9fd895925ac77f566f1)
Co-authored-by: Ćukasz Langa <lukasz@langa.pl>
Diffstat (limited to 'Lib/_pyrepl/_threading_handler.py')
| -rw-r--r-- | Lib/_pyrepl/_threading_handler.py | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/Lib/_pyrepl/_threading_handler.py b/Lib/_pyrepl/_threading_handler.py new file mode 100644 index 0000000..82f5e86 --- /dev/null +++ b/Lib/_pyrepl/_threading_handler.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +import traceback + + +TYPE_CHECKING = False +if TYPE_CHECKING: + from threading import Thread + from types import TracebackType + from typing import Protocol + + class ExceptHookArgs(Protocol): + @property + def exc_type(self) -> type[BaseException]: ... + @property + def exc_value(self) -> BaseException | None: ... + @property + def exc_traceback(self) -> TracebackType | None: ... + @property + def thread(self) -> Thread | None: ... + + class ShowExceptions(Protocol): + def __call__(self) -> int: ... + def add(self, s: str) -> None: ... + + from .reader import Reader + + +def install_threading_hook(reader: Reader) -> None: + import threading + + @dataclass + class ExceptHookHandler: + lock: threading.Lock = field(default_factory=threading.Lock) + messages: list[str] = field(default_factory=list) + + def show(self) -> int: + count = 0 + with self.lock: + if not self.messages: + return 0 + reader.restore() + for tb in self.messages: + count += 1 + if tb: + print(tb) + self.messages.clear() + reader.scheduled_commands.append("ctrl-c") + reader.prepare() + return count + + def add(self, s: str) -> None: + with self.lock: + self.messages.append(s) + + def exception(self, args: ExceptHookArgs) -> None: + lines = traceback.format_exception( + args.exc_type, + args.exc_value, + args.exc_traceback, + colorize=reader.can_colorize, + ) # type: ignore[call-overload] + pre = f"\nException in {args.thread.name}:\n" if args.thread else "\n" + tb = pre + "".join(lines) + self.add(tb) + + def __call__(self) -> int: + return self.show() + + + handler = ExceptHookHandler() + reader.threading_hook = handler + threading.excepthook = handler.exception |
