From 210ce8ca7cb16e1d0ce859baa64ce56a5e1894cf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Mar 2015 23:23:49 +0100 Subject: [PATCH] Don't poll for signals on Unix. A better solution is to use QSocketNotifier and os.wakeup_fd to get notified about new signals. Thanks to Yuya Nishihara / TortoiseHG for the hint! Fixes #555. --- qutebrowser/app.py | 54 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 829aee94c..e95917d92 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -34,7 +34,7 @@ import faulthandler from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, - QObject, Qt) + QObject, Qt, QSocketNotifier) import qutebrowser import qutebrowser.resources # pylint: disable=unused-import @@ -63,6 +63,8 @@ class Application(QApplication): _crashdlg: The crash dialog currently open. _crashlogfile: A file handler to the fatal crash logfile. _event_filter: The EventFilter for the application. + _signal_notifier: A QSocketNotifier used for signals on Unix. + _signal_timer: A QTimer used to poll for signals on Windows. geometry: The geometry of the last closed main window. """ @@ -146,8 +148,8 @@ class Application(QApplication): log.init.debug("Connecting signals...") self._connect_signals() - log.init.debug("Applying python hacks...") - self._python_hacks() + log.init.debug("Setting up signal handlers...") + self._setup_signals() QDesktopServices.setUrlHandler('http', self.open_desktopservices_url) QDesktopServices.setUrlHandler('https', self.open_desktopservices_url) @@ -413,19 +415,47 @@ class Application(QApplication): pass state_config['general']['quickstart-done'] = '1' - def _python_hacks(self): - """Get around some PyQt-oddities by evil hacks. + def _setup_signals(self): + """Set up signal handlers. - This sets up the uncaught exception hook, quits with an appropriate - exit status, and handles Ctrl+C properly by passing control to the - Python interpreter once all 500ms. + On Windows this uses a QTimer to periodically hand control over to + Python so it can handle signals. + + On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get + notified. """ signal.signal(signal.SIGINT, self.interrupt) signal.signal(signal.SIGTERM, self.interrupt) - timer = usertypes.Timer(self, 'python_hacks') - timer.start(500) - timer.timeout.connect(lambda: None) - objreg.register('python-hack-timer', timer) + + if os.name == 'posxix' and hasattr(signal, 'set_wakeup_fd'): + import fcntl + read_fd, write_fd = os.pipe() + for fd in (read_fd, write_fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + self._signal_notifier = QSocketNotifier( + read_fd, QSocketNotifier.Read, self) + self._signal_notifier.activated.connect(self._handle_signal_wakeup) + signal.set_wakeup_fd(write_fd) + else: + self._signal_timer = usertypes.Timer(self, 'python_hacks') + self._signal_timer.start(1000) + self._signal_timer.timeout.connect(lambda: None) + + @pyqtSlot() + def _handle_signal_wakeup(self): + """This gets called via self._signal_notifier when there's a signal. + + Python will get control here, so the signal will get handled. + """ + log.destroy.debug("Handling signal wakeup!") + self._signal_notifier.setEnabled(False) + read_fd = self._signal_notifier.socket() + try: + os.read(read_fd, 1) + except OSError: + log.destroy.exception("Failed to read wakeup fd.") + self._signal_notifier.setEnabled(True) def _connect_signals(self): """Connect all signals to their slots."""