From 659fe5126bd70bd8277e48c6c5f227eabfbe0b00 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 May 2014 12:20:03 +0200 Subject: [PATCH] Display error dialog when started after segfault --- qutebrowser/app.py | 35 ++++++++++++++++++++-- qutebrowser/utils/earlyinit.py | 6 ++-- qutebrowser/widgets/crash.py | 53 ++++++++++++++++++++++++++++------ 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3032b0bb0..10545cd0f 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -31,6 +31,7 @@ import sys import types import logging import subprocess +import faulthandler import configparser from bdb import BdbQuit from functools import partial @@ -54,7 +55,7 @@ from qutebrowser.network.networkmanager import NetworkManager from qutebrowser.config.config import ConfigManager from qutebrowser.keyinput.modeman import ModeManager from qutebrowser.widgets.mainwindow import MainWindow -from qutebrowser.widgets.crash import ExceptionCrashDialog +from qutebrowser.widgets.crash import ExceptionCrashDialog, FatalCrashDialog from qutebrowser.keyinput.modeparsers import NormalKeyParser, HintKeyParser from qutebrowser.keyinput.keyparser import PassthroughKeyParser from qutebrowser.commands.managers import CommandManager, SearchManager @@ -94,6 +95,8 @@ class QuteBrowser(QApplication): _shutting_down: True if we're currently shutting down. _quit_status: The current quitting status. _opened_urls: List of opened URLs. + _crashdlg: The crash dialog currently open. + _crashlogfile: A file handler to the fatal crash logfile. """ # This also holds all our globals, so we're a bit over the top here. @@ -110,6 +113,8 @@ class QuteBrowser(QApplication): self._opened_urls = [] self._shutting_down = False self._keyparsers = None + self._crashdlg = None + self._crashlogfile = None self.messagebridge = None self.stateconfig = None self.modeman = None @@ -123,6 +128,7 @@ class QuteBrowser(QApplication): self._init_misc() actute_warning() self._init_config() + self._handle_segfault() self._init_modes() websettings.init() proxy.init() @@ -145,6 +151,9 @@ class QuteBrowser(QApplication): timer = QTimer.singleShot(0, self._process_init_args) self._timers.append(timer) + if self._crashdlg is not None: + self._crashdlg.raise_() + def _parse_args(self): """Parse command line options. @@ -238,6 +247,26 @@ class QuteBrowser(QApplication): self.setApplicationVersion(qutebrowser.__version__) self.messagebridge = MessageBridge() + def _handle_segfault(self): + """Handle a segfault from a previous run.""" + logname = os.path.join(get_standard_dir(QStandardPaths.DataLocation), + 'crash.log') + # First check if an old logfile exists. + if os.path.exists(logname): + with open(logname, 'r') as f: + data = f.read() + # Just in case FatalCrashDialog crashes... -.- + os.remove(logname) + if data: + self._crashdlg = FatalCrashDialog(data) + self._crashdlg.show() + # Start a new logfile and redirect faulthandler to it + self._crashlogfile = open(logname, 'w') # This also truncates. + faulthandler.enable(self._crashlogfile) + if hasattr(faulthandler, 'register') and hasattr(signal, 'SIGUSR1'): + # If available, we also want a traceback on SIGUSR1. + faulthandler.register(signal.SIGUSR1) # pylint: disable=no-member + def _init_cmds(self): """Initialisation of the qutebrowser commands. @@ -427,8 +456,8 @@ class QuteBrowser(QApplication): except TypeError: logging.exception("Preventing shutdown failed.") QApplication.closeAllWindows() - dlg = ExceptionCrashDialog(pages, history, exc) - ret = dlg.exec_() + self._crashdlg = ExceptionCrashDialog(pages, history, exc) + ret = self._crashdlg.exec_() if ret == QDialog.Accepted: # restore self.restart(shutdown=False, pages=pages) # We might risk a segfault here, but that's better than continuing to diff --git a/qutebrowser/utils/earlyinit.py b/qutebrowser/utils/earlyinit.py index 7206c7a02..7d7ef23df 100644 --- a/qutebrowser/utils/earlyinit.py +++ b/qutebrowser/utils/earlyinit.py @@ -39,9 +39,9 @@ def init_faulthandler(): # If we'd enable faulthandler in that case, we just get a weird # exception, so we don't enable faulthandler if we have no stdout. # - # FIXME at the point we have our config/data dirs, we probably should - # re-enable faulthandler to write to a file. Then we can also display - # crashes to the user at the next start. + # Later when we have our data dir available we re-enable faulthandler + # to write to a file so we can display a crash to the user at the next + # start. return faulthandler.enable() if hasattr(faulthandler, 'register') and hasattr(signal, 'SIGUSR1'): diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index 82f82e330..de0481951 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -176,29 +176,64 @@ class ExceptionCrashDialog(_CrashDialog): self._btn_quit = QPushButton() self._btn_quit.setText("Quit") self._btn_quit.clicked.connect(self.reject) + self._hbox.addWidget(self._btn_quit) self._btn_pastebin = QPushButton() self._btn_pastebin.setText("Pastebin") self._btn_pastebin.clicked.connect(self.pastebin) - self._hbox.addWidget(self._btn_quit) + self._hbox.addWidget(self._btn_pastebin) if self._pages: self._btn_restore = QPushButton() self._btn_restore.setText("Restore tabs") self._btn_restore.clicked.connect(self.accept) self._btn_restore.setDefault(True) self._hbox.addWidget(self._btn_restore) - self._hbox.addWidget(self._btn_pastebin) def _gather_crash_info(self): - """Gather crash information to display. - - Args: - pages: A list of the open pages (URLs as strings) - cmdhist: A list with the command history (as strings) - exc: An exception tuple (type, value, traceback) - """ super()._gather_crash_info() self._crash_info += [ ("Exception", ''.join(traceback.format_exception(*self._exc))), ("Open Pages", '\n'.join(self._pages)), ("Command history", '\n'.join(self._cmdhist)), ] + + +class FatalCrashDialog(_CrashDialog): + + """Dialog which gets shown when a fatal error occured. + + Attributes: + _log: The log text to display. + _btn_ok: The OK button. + _btn_pastebin: The pastebin button. + """ + + def __init__(self, log): + self._log = log + self._btn_ok = None + self._btn_pastebin = None + super().__init__() + + def _init_text(self): + super()._init_text() + text = ("qutebrowser was restarted after a fatal crash.
" + "Please click on 'pastebin' or send the data below to " + "" + "crash@qutebrowser.org.

") + self._lbl.setText(text) + + def _init_buttons(self): + super()._init_buttons() + self._btn_ok = QPushButton() + self._btn_ok.setText("OK") + self._btn_ok.clicked.connect(self.accept) + self._hbox.addWidget(self._btn_ok) + self._btn_pastebin = QPushButton() + self._btn_pastebin.setText("Pastebin") + self._btn_pastebin.clicked.connect(self.pastebin) + self._hbox.addWidget(self._btn_pastebin) + + def _gather_crash_info(self): + super()._gather_crash_info() + self._crash_info += [ + ("Fault log", self._log), + ]