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.
This commit is contained in:
Florian Bruhin 2015-03-16 23:23:49 +01:00
parent 6dc65287a9
commit 210ce8ca7c

View File

@ -34,7 +34,7 @@ import faulthandler
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QObject, Qt) QObject, Qt, QSocketNotifier)
import qutebrowser import qutebrowser
import qutebrowser.resources # pylint: disable=unused-import import qutebrowser.resources # pylint: disable=unused-import
@ -63,6 +63,8 @@ class Application(QApplication):
_crashdlg: The crash dialog currently open. _crashdlg: The crash dialog currently open.
_crashlogfile: A file handler to the fatal crash logfile. _crashlogfile: A file handler to the fatal crash logfile.
_event_filter: The EventFilter for the application. _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. geometry: The geometry of the last closed main window.
""" """
@ -146,8 +148,8 @@ class Application(QApplication):
log.init.debug("Connecting signals...") log.init.debug("Connecting signals...")
self._connect_signals() self._connect_signals()
log.init.debug("Applying python hacks...") log.init.debug("Setting up signal handlers...")
self._python_hacks() self._setup_signals()
QDesktopServices.setUrlHandler('http', self.open_desktopservices_url) QDesktopServices.setUrlHandler('http', self.open_desktopservices_url)
QDesktopServices.setUrlHandler('https', self.open_desktopservices_url) QDesktopServices.setUrlHandler('https', self.open_desktopservices_url)
@ -413,19 +415,47 @@ class Application(QApplication):
pass pass
state_config['general']['quickstart-done'] = '1' state_config['general']['quickstart-done'] = '1'
def _python_hacks(self): def _setup_signals(self):
"""Get around some PyQt-oddities by evil hacks. """Set up signal handlers.
This sets up the uncaught exception hook, quits with an appropriate On Windows this uses a QTimer to periodically hand control over to
exit status, and handles Ctrl+C properly by passing control to the Python so it can handle signals.
Python interpreter once all 500ms.
On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get
notified.
""" """
signal.signal(signal.SIGINT, self.interrupt) signal.signal(signal.SIGINT, self.interrupt)
signal.signal(signal.SIGTERM, self.interrupt) signal.signal(signal.SIGTERM, self.interrupt)
timer = usertypes.Timer(self, 'python_hacks')
timer.start(500) if os.name == 'posxix' and hasattr(signal, 'set_wakeup_fd'):
timer.timeout.connect(lambda: None) import fcntl
objreg.register('python-hack-timer', timer) 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): def _connect_signals(self):
"""Connect all signals to their slots.""" """Connect all signals to their slots."""