mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
fix: exit on the window's signal when it closes
A closed window under a changing screen failed the draw (EIO, or a termios.error from the SIGCONT that comes with SIGHUP) before the signal was read, and the run ended on 1 with a crash log kept.
This commit is contained in:
1 parent
58c239deb4
commit
cf3b61a17b
4 files changed
+66
-13
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "e2cc5f073a8226fe26b0513ebd25f00de6f3f613d8c9851473c2d30b53356f48"
|
||||
$defaultInstallSha256 = "c2a66e6b26004e96ef5f59a7fb5a2ed65bcaa9afc7602bd3e0d71f50cc561056"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+34
-7
@@ -12500,13 +12500,14 @@ def run_tui(args: argparse.Namespace) -> int:
|
||||
if terminating["signal"] and state.screen == "result":
|
||||
# The result is printed in the terminal on the way out.
|
||||
state.exit_code = state.code if state.code is not None else 1
|
||||
except Exception: # noqa: BLE001
|
||||
# A traceback is where every reporter of #71, #73 and #74 stopped:
|
||||
# it goes to a file, and the terminal says one sentence.
|
||||
crash = work / "error.log"
|
||||
crash.write_text(traceback.format_exc(), encoding="utf-8")
|
||||
state.log, state.code, state.exit_code = str(crash), 1, 1
|
||||
state.stack = ["crashed"]
|
||||
except Exception as exc: # noqa: BLE001
|
||||
if flags["term"] or _closed_terminal(exc):
|
||||
# The window closed under a screen that was changing, or the
|
||||
# SIGCONT that comes with its SIGHUP put the screen back: the
|
||||
# write failed before the signal was read. That is its exit.
|
||||
state.exit_code = 128 + (flags["term"] or getattr(signal, "SIGHUP", 1))
|
||||
else:
|
||||
_tui_crashed(state, work)
|
||||
finally:
|
||||
# Each step on its own: a write the closed window refuses, or a
|
||||
# second signal while the install writes its summary, must not
|
||||
@@ -12560,6 +12561,32 @@ def _tui_startup(args: argparse.Namespace) -> tuple[TuiState, list[tuple]] | Non
|
||||
return tui_start(args, os_type)
|
||||
|
||||
|
||||
# What a write to a closed window or a closed pipe answers.
|
||||
_CLOSED_TERMINAL = frozenset({errno.EIO, errno.EPIPE, errno.ENXIO})
|
||||
|
||||
|
||||
def _closed_terminal(exc: BaseException) -> bool:
|
||||
"""Whether exc is the terminal gone: an OSError, or a termios.error,
|
||||
whose first argument is the errno, answering EIO, EPIPE or ENXIO."""
|
||||
if isinstance(exc, OSError):
|
||||
return exc.errno in _CLOSED_TERMINAL
|
||||
if type(exc).__name__ == "error" and type(exc).__module__ == "termios":
|
||||
return bool(exc.args) and exc.args[0] in _CLOSED_TERMINAL
|
||||
return False
|
||||
|
||||
|
||||
def _tui_crashed(state: TuiState, work: Path) -> None:
|
||||
"""A traceback is where every reporter of #71, #73 and #74 stopped: it
|
||||
goes to a file, and the terminal says one sentence."""
|
||||
crash = work / "error.log"
|
||||
try:
|
||||
crash.write_text(traceback.format_exc(), encoding="utf-8")
|
||||
except OSError as exc:
|
||||
print(f"The error could not be written to {crash}: {exc}", file=sys.stderr)
|
||||
state.log, state.code, state.exit_code = str(crash), 1, 1
|
||||
state.stack = ["crashed"]
|
||||
|
||||
|
||||
def _restore_handlers(handlers: dict) -> None:
|
||||
for number, previous in handlers.items():
|
||||
signal.signal(number, previous)
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="e2cc5f073a8226fe26b0513ebd25f00de6f3f613d8c9851473c2d30b53356f48"
|
||||
DEFAULT_INSTALL_SHA256="c2a66e6b26004e96ef5f59a7fb5a2ed65bcaa9afc7602bd3e0d71f50cc561056"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -1444,13 +1444,14 @@ class CleanupSurvivesABrokenTerminal(unittest.TestCase):
|
||||
mock.patch.object(sys, "stderr", out):
|
||||
code = install.run_tui(_args())
|
||||
self.assertIs(sys.stdout, out)
|
||||
self.assertEqual(code, 1)
|
||||
# A window that answers EIO is a closed window: its signal's code,
|
||||
# not a crash.
|
||||
self.assertEqual(code, 129)
|
||||
# The farewell found no terminal: what still flushes at exit goes to
|
||||
# the null device, and the exit code stays the one returned.
|
||||
self.assertEqual(os.fstat(screen_fd).st_rdev, os.stat(os.devnull).st_rdev)
|
||||
# The crash keeps its log, marked kept, and nothing else of the run.
|
||||
self.assertEqual(sorted(path.name for path in work.iterdir()),
|
||||
["error.log", "kept", "owner"])
|
||||
# No crash log is kept, and nothing else of the run.
|
||||
self.assertFalse(work.exists())
|
||||
self.assertEqual(termios.tcgetattr(slave), before)
|
||||
for number, handler in handlers.items():
|
||||
self.assertEqual(signal.getsignal(number), handler)
|
||||
@@ -2859,6 +2860,31 @@ class EndToEnd(unittest.TestCase):
|
||||
self.assertTrue(os.WIFEXITED(status))
|
||||
self.assertEqual(term.status, 129)
|
||||
|
||||
def test_closing_the_window_while_it_installs(self):
|
||||
"""The changing screen fails to draw on the closed window: that is
|
||||
the window's signal, not a crash with a log kept."""
|
||||
repo = self.repo(GameDataRepository, slow=True)
|
||||
scratch = repo.root / "scratch"
|
||||
scratch.mkdir()
|
||||
self.env["TMPDIR"] = str(scratch)
|
||||
term = self.start("--platform", "retroarch", "--dest", str(repo.dest), "--jobs", "1")
|
||||
term.wait("> Install")
|
||||
term.send("\r")
|
||||
term.wait("1 of 6 files")
|
||||
os.close(term.fd)
|
||||
end = time.monotonic() + 20
|
||||
while time.monotonic() < end:
|
||||
pid, status = os.waitpid(term.pid, os.WNOHANG)
|
||||
if pid:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
else:
|
||||
os.kill(term.pid, 9)
|
||||
self.fail("still running once its window closed")
|
||||
term.status = os.WEXITSTATUS(status)
|
||||
self.assertEqual(term.status, 129)
|
||||
self.assertEqual(list(scratch.glob("retrobios-tui-*/error.log")), [])
|
||||
|
||||
def test_a_desktop_names_its_keys_and_a_pad_its_buttons(self):
|
||||
self.repo()
|
||||
for env, bar in (({"DISPLAY": ":0"}, "Enter Continue Esc Quit ? Help"),
|
||||
|
||||
Reference in new issue
Block a user