Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions Lib/_pyrepl/unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,14 @@ def prepare(self):
raw.lflag |= termios.ISIG
raw.cc[termios.VMIN] = 1
raw.cc[termios.VTIME] = 0
tcsetattr(self.input_fd, termios.TCSADRAIN, raw)
try:
tcsetattr(self.input_fd, termios.TCSADRAIN, raw)
except termios.error as e:
if e.args[0] != errno.EIO:
# gh-135329: when running under external programs (like strace),
# tcsetattr may fail with EIO. We can safely ignore this
# and continue with default terminal settings.
raise

# In macOS terminal we need to deactivate line wrap via ANSI escape code
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
Expand Down Expand Up @@ -368,7 +375,11 @@ def restore(self):
self.__disable_bracketed_paste()
self.__maybe_write_code(self._rmkx)
self.flushoutput()
tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
try:
tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
except termios.error as e:
if e.args[0] != errno.EIO:
raise

Comment thread
yihong0618 marked this conversation as resolved.
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
os.write(self.output_fd, b"\033[?7h")
Expand Down Expand Up @@ -407,6 +418,9 @@ def get_event(self, block: bool = True) -> Event | None:
return self.event_queue.get()
else:
continue
elif err.errno == errno.EIO:
import sys
sys.exit(errno.EIO)
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
else:
raise
else:
Expand Down
114 changes: 113 additions & 1 deletion Lib/test/test_pyrepl/test_unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
import os
import sys
import unittest
import errno
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
import termios
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
from functools import partial
from test.support import os_helper, force_not_colorized_test_class
import subprocess
import time
import shutil
import signal

from unittest import TestCase
from unittest.mock import MagicMock, call, patch, ANY
from unittest.mock import MagicMock, call, patch, ANY, Mock

from .support import handle_all_events, code_to_events

Expand Down Expand Up @@ -303,3 +309,109 @@ def test_getheightwidth_with_invalid_environ(self, _os_write):
self.assertIsInstance(console.getheightwidth(), tuple)
os.environ = []
self.assertIsInstance(console.getheightwidth(), tuple)


class TestUnixConsoleEIOHandling(TestCase):

@patch('_pyrepl.unix_console.tcsetattr')
@patch('_pyrepl.unix_console.tcgetattr')
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
def test_eio_error_handling_in_prepare(self, mock_tcgetattr, mock_tcsetattr):
mock_termios = Mock()
mock_termios.iflag = 0
mock_termios.oflag = 0
mock_termios.cflag = 0
mock_termios.lflag = 0
mock_termios.cc = [0] * 32
mock_termios.copy.return_value = mock_termios
mock_tcgetattr.return_value = mock_termios

mock_tcsetattr.side_effect = termios.error(errno.EIO, "Input/output error")

console = UnixConsole(term="xterm")

try:
console.prepare()
except termios.error as e:
if e.args[0] == errno.EIO:
self.fail("EIO error should have been handled gracefully in prepare()")
raise

@patch('_pyrepl.unix_console.tcsetattr')
@patch('_pyrepl.unix_console.tcgetattr')
def test_eio_error_handling_in_restore(self, mock_tcgetattr, mock_tcsetattr):

mock_termios = Mock()
mock_termios.iflag = 0
mock_termios.oflag = 0
mock_termios.cflag = 0
mock_termios.lflag = 0
mock_termios.cc = [0] * 32
mock_termios.copy.return_value = mock_termios
mock_tcgetattr.return_value = mock_termios

console = UnixConsole(term="xterm")
console.prepare()

mock_tcsetattr.side_effect = termios.error(errno.EIO, "Input/output error")

try:
console.restore()
except termios.error as e:
if e.args[0] == errno.EIO:
self.fail("EIO error should have been handled gracefully in restore()")
raise
Comment thread
yihong0618 marked this conversation as resolved.
Outdated

class TestEIOWithStrace(unittest.TestCase):
def setUp(self):
self.strace = shutil.which("strace")
if not self.strace:
self.skipTest("strace")

def _attach_strace(self, pid):
cmd = [self.strace, "-qq", "-p", str(pid), "-e", "inject=read:error=EIO:when=1", "-o", "/dev/null"]
try:
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
time.sleep(0.15)
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
if p.poll() is None:
return p
except Exception:
pass
Comment thread
yihong0618 marked this conversation as resolved.
Outdated

cmd = [self.strace, "-qq", "-p", str(pid), "-e", "fault=read:error=EIO:when=1", "-o", "/dev/null"]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
time.sleep(0.15)
return p

def test_repl_eio(self):
pybin = sys.executable

child_code = r"""
import signal, sys
signal.signal(signal.SIGUSR1, lambda *a: None)
print("READY", flush=True)
signal.pause()
input()
"""
Comment thread
yihong0618 marked this conversation as resolved.
Outdated

proc = subprocess.Popen(
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
[pybin, "-S", "-c", child_code],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)

line = proc.stdout.readline().strip()
Comment thread
yihong0618 marked this conversation as resolved.
Outdated
self.assertEqual(line, "READY")

tracer = self._attach_strace(proc.pid)

try:
os.kill(proc.pid, signal.SIGUSR1)
_, err = proc.communicate(timeout=5)
finally:
if tracer and tracer.poll() is None:
tracer.terminate()

self.assertNotEqual(proc.returncode, 0)
self.assertTrue("Errno 5" in err or "Input/output error" in err or "EOFError" in err, err)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prevent infinite traceback loop when sending CTRL^C to Python through ``strace``.
Loading