Linux/OSX: Shutdown gracefully on receiving interrupt or terminate signals

This commit is contained in:
Kovid Goyal 2016-08-03 11:14:39 +05:30
parent 57efd828ef
commit ece360f04c
8 changed files with 39 additions and 8 deletions

View File

@ -1,13 +1,13 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """
import os, sys, Queue, threading, glob
import os, sys, Queue, threading, glob, signal
from contextlib import contextmanager
from threading import RLock, Lock
from urllib import unquote
from PyQt5.QtWidgets import QStyle # Gives a nicer error message than import from Qt
from PyQt5.Qt import (
QFileInfo, QObject, QBuffer, Qt, QByteArray, QTranslator,
QFileInfo, QObject, QBuffer, Qt, QByteArray, QTranslator, QSocketNotifier,
QCoreApplication, QThread, QEvent, QTimer, pyqtSignal, QDateTime,
QDesktopServices, QFileDialog, QFileIconProvider, QSettings, QIcon,
QApplication, QDialog, QUrl, QFont, QFontDatabase, QLocale, QFontInfo)
@ -860,6 +860,8 @@ def setup_gui_option_parser(parser):
class Application(QApplication):
shutdown_signal_received = pyqtSignal()
def __init__(self, args, force_calibre_style=False, override_program_name=None, headless=False, color_prefs=gprefs):
self.file_event_hook = None
if override_program_name:
@ -872,6 +874,8 @@ class Application(QApplication):
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
self.pi = plugins['progress_indicator'][0]
QApplication.__init__(self, qargs)
if not iswindows:
self.setup_unix_signals()
if islinux or isbsd:
self.setAttribute(Qt.AA_DontUseNativeMenuBar, 'CALIBRE_NO_NATIVE_MENUBAR' in os.environ)
self.setup_styles(force_calibre_style)
@ -1048,6 +1052,28 @@ class Application(QApplication):
def __exit__(self, *args):
self.setQuitOnLastWindowClosed(True)
def setup_unix_signals(self):
import fcntl
read_fd, write_fd = os.pipe()
cloexec_flag = getattr(fcntl, 'FD_CLOEXEC', 1)
for fd in (read_fd, write_fd):
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | cloexec_flag | os.O_NONBLOCK)
for sig in (signal.SIGINT, signal.SIGTERM):
signal.signal(sig, lambda x, y: None)
signal.siginterrupt(sig, False)
signal.set_wakeup_fd(write_fd)
self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self)
self.signal_notifier.setEnabled(True)
self.signal_notifier.activated.connect(self.signal_received, type=Qt.QueuedConnection)
def signal_received(self, read_fd):
try:
os.read(read_fd, 1024)
except EnvironmentError:
return
self.shutdown_signal_received.emit()
_store_app = None
@contextmanager

View File

@ -86,7 +86,6 @@ class MainWindow(QMainWindow):
___menu = None
__actions = []
keyboard_interrupt = pyqtSignal()
# See https://bugreports.qt-project.org/browse/QTBUG-42281
window_blocked = pyqtSignal()
window_unblocked = pyqtSignal()
@ -131,8 +130,7 @@ class MainWindow(QMainWindow):
sys.excepthook = ExceptionHandler(self)
def unhandled_exception(self, type, value, tb):
if type == KeyboardInterrupt:
self.keyboard_interrupt.emit()
if type is KeyboardInterrupt:
return
try:
sio = StringIO.StringIO()

View File

@ -178,7 +178,7 @@ def main(control_conn, data_conn):
while True:
try:
request = eintr_retry_call(control_conn.recv)
except EOFError:
except (KeyboardInterrupt, EOFError):
break
if request is None:
break

View File

@ -71,6 +71,7 @@ def _run(args, notify=None):
main = Main(opts, notify=notify)
main.set_exception_handler()
main.show()
app.shutdown_signal_received.connect(main.boss.quit)
if len(args) > 1:
main.boss.open_book(args[1], edit_file=args[2:], clear_notify_data=False)
else:

View File

@ -397,8 +397,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if config['autolaunch_server']:
self.start_content_server()
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
self.read_settings()
self.finalize_layout()
if self.bars_manager.showing_donate:
@ -425,6 +423,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
# Collect cycles now
gc.collect()
QApplication.instance().shutdown_signal_received.connect(self.quit)
if show_gui and self.gui_debug is not None:
QTimer.singleShot(10, self.show_gui_debug_msg)
@ -843,6 +842,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def quit(self, checked=True, restart=False, debug_on_restart=False,
confirm_quit=True):
if self.shutting_down:
return
if confirm_quit and not self.confirm_quit():
return
try:

View File

@ -178,6 +178,7 @@ class EbookViewer(MainWindow):
self.action_reload = QAction(_('&Reload book'), self)
self.action_reload.triggered.connect(self.reload_book)
self.action_quit.triggered.connect(self.quit)
QApplication.instance().shutdown_signal_received.connect(self.action_quit.trigger)
self.action_reference_mode.triggered[bool].connect(self.view.reference_mode)
self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible)

View File

@ -337,6 +337,8 @@ def worker_main(conn):
job = cPickle.loads(eintr_retry_call(conn.recv_bytes))
except EOFError:
break
except KeyboardInterrupt:
break
except Exception:
prints('recv() failed in worker, terminating worker', file=sys.stderr)
import traceback

View File

@ -70,6 +70,8 @@ class OffloadWorker(object):
def shutdown(self):
try:
eintr_retry_call(self.conn.send, None)
except IOError:
pass
except:
import traceback
traceback.print_exc()