summaryrefslogtreecommitdiffstats
path: root/Lib/_pyrepl/_threading_handler.py
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-09-06 20:25:19 (GMT)
committerGitHub <noreply@github.com>2024-09-06 20:25:19 (GMT)
commit5c3078d6e597c7e50b3b0da37f493e2dfca17a6a (patch)
treec8f16cbf540fee5b7527d51b3b9ff7ed9c8b065e /Lib/_pyrepl/_threading_handler.py
parent66b15381f187f00f0fd91575f9f11e14bfddeeca (diff)
downloadcpython-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.py74
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