Display error dialog when started after segfault

This commit is contained in:
Florian Bruhin 2014-05-15 12:20:03 +02:00
parent 8fe4000e41
commit 659fe5126b
3 changed files with 79 additions and 15 deletions

View File

@ -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

View File

@ -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'):

View File

@ -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.<br/>"
"Please click on 'pastebin' or send the data below to "
"<a href='mailto:crash@qutebrowser.org'>"
"crash@qutebrowser.org</a>.<br/><br/>")
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),
]