|
28 | 28 | import signal |
29 | 29 | import struct |
30 | 30 | import termios |
| 31 | +import threading |
31 | 32 | import time |
32 | 33 | import types |
33 | 34 | import platform |
@@ -157,6 +158,8 @@ def __init__( |
157 | 158 |
|
158 | 159 | self.pollob = poll() |
159 | 160 | self.pollob.register(self.input_fd, select.POLLIN) |
| 161 | + self._poll_lock = threading.RLock() |
| 162 | + self._polling_thread = None |
160 | 163 | self.terminfo = terminfo.TermInfo(term or None) |
161 | 164 | self.term = term |
162 | 165 |
|
@@ -325,8 +328,11 @@ def prepare(self): |
325 | 328 | """ |
326 | 329 | Prepare the console for input/output operations. |
327 | 330 | """ |
328 | | - self.__svtermstate = tcgetattr(self.input_fd) |
329 | | - raw = self.__svtermstate.copy() |
| 331 | + # gh-130168: prevents signal handlers from overwriting the original state |
| 332 | + if not hasattr(self, '_UnixConsole__svtermstate'): |
| 333 | + self.__svtermstate = tcgetattr(self.input_fd) |
| 334 | + |
| 335 | + raw = tcgetattr(self.input_fd).copy() |
330 | 336 | raw.iflag &= ~(termios.INPCK | termios.ISTRIP | termios.IXON) |
331 | 337 | raw.oflag &= ~(termios.OPOST) |
332 | 338 | raw.cflag &= ~(termios.CSIZE | termios.PARENB) |
@@ -368,7 +374,11 @@ def restore(self): |
368 | 374 | self.__disable_bracketed_paste() |
369 | 375 | self.__maybe_write_code(self._rmkx) |
370 | 376 | self.flushoutput() |
371 | | - tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate) |
| 377 | + |
| 378 | + if hasattr(self, '_UnixConsole__svtermstate'): |
| 379 | + tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate) |
| 380 | + # Clear the saved state so prepare() can save a fresh one next time |
| 381 | + del self.__svtermstate |
372 | 382 |
|
373 | 383 | if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal": |
374 | 384 | os.write(self.output_fd, b"\033[?7h") |
@@ -417,10 +427,25 @@ def wait(self, timeout: float | None = None) -> bool: |
417 | 427 | """ |
418 | 428 | Wait for events on the console. |
419 | 429 | """ |
420 | | - return ( |
421 | | - not self.event_queue.empty() |
422 | | - or bool(self.pollob.poll(timeout)) |
423 | | - ) |
| 430 | + if not self.event_queue.empty(): |
| 431 | + return True |
| 432 | + |
| 433 | + current_thread = threading.current_thread() |
| 434 | + |
| 435 | + if self._polling_thread is current_thread: |
| 436 | + # This is a re-entrant call from the same thread |
| 437 | + # like old repl runtime error |
| 438 | + raise RuntimeError("can't re-enter readline") |
| 439 | + |
| 440 | + if not self._poll_lock.acquire(blocking=False): |
| 441 | + return False |
| 442 | + |
| 443 | + try: |
| 444 | + self._polling_thread = current_thread |
| 445 | + return bool(self.pollob.poll(timeout)) |
| 446 | + finally: |
| 447 | + self._polling_thread = None |
| 448 | + self._poll_lock.release() |
424 | 449 |
|
425 | 450 | def set_cursor_vis(self, visible): |
426 | 451 | """ |
@@ -786,7 +811,10 @@ def __tputs(self, fmt, prog=delayprog): |
786 | 811 | # using .get() means that things will blow up |
787 | 812 | # only if the bps is actually needed (which I'm |
788 | 813 | # betting is pretty unlkely) |
789 | | - bps = ratedict.get(self.__svtermstate.ospeed) |
| 814 | + if hasattr(self, '_UnixConsole__svtermstate'): |
| 815 | + bps = ratedict.get(self.__svtermstate.ospeed) |
| 816 | + else: |
| 817 | + bps = None |
790 | 818 | while True: |
791 | 819 | m = prog.search(fmt) |
792 | 820 | if not m: |
|
0 commit comments