From b505225c907af2f2e51b42959f2dab1b12a26b1e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 24 Aug 2014 23:56:36 +0530 Subject: [PATCH] Possible fix for crash on some linux systems in Qt 5 with --detach Do the fork() before loading any Qt modules. --- src/calibre/gui2/__init__.py | 17 ---------- src/calibre/gui2/main.py | 4 +-- src/calibre/gui2/tweak_book/main.py | 4 +-- src/calibre/gui2/viewer/main.py | 4 +-- src/calibre/gui_launch.py | 51 +++++++++++++++++++++++++++++ src/calibre/linux.py | 8 ++--- 6 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 src/calibre/gui_launch.py diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index b6dd4be57b..ca9fcb0020 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -874,23 +874,6 @@ def setup_gui_option_parser(parser): parser.add_option('--detach', default=False, action='store_true', help=_('Detach from the controlling terminal, if any (linux only)')) -def do_detach(fork=True, setsid=True, redirect=True): - if fork: - # Detach from the controlling process. - if os.fork() != 0: - raise SystemExit(0) - if setsid: - os.setsid() - if redirect: - try: - plugins['speedup'][0].detach(os.devnull) - except AttributeError: - pass # people running from source without updated binaries - -def detach_gui(): - if islinux and not DEBUG: - do_detach() - class Application(QApplication): def __init__(self, args, force_calibre_style=False, override_program_name=None, headless=False): diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index bfeeda8ee0..f6ae4263e7 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -13,7 +13,7 @@ from calibre.constants import (iswindows, __appname__, isosx, DEBUG, islinux, from calibre.utils.ipc import gui_socket_address, RC from calibre.gui2 import ( ORG_NAME, APP_UID, initialize_file_icon_provider, Application, choose_dir, - error_dialog, question_dialog, gprefs, detach_gui, setup_gui_option_parser) + error_dialog, question_dialog, gprefs, setup_gui_option_parser) from calibre.gui2.main_window import option_parser as _option_parser from calibre.gui2.splash import SplashScreen from calibre.utils.config import prefs, dynamic @@ -86,8 +86,6 @@ def find_portable_library(): def init_qt(args): parser = option_parser() opts, args = parser.parse_args(args) - if getattr(opts, 'detach', False): - detach_gui() find_portable_library() if opts.with_library is not None: libpath = os.path.expanduser(opts.with_library) diff --git a/src/calibre/gui2/tweak_book/main.py b/src/calibre/gui2/tweak_book/main.py index a3755dd49c..43dc237a11 100644 --- a/src/calibre/gui2/tweak_book/main.py +++ b/src/calibre/gui2/tweak_book/main.py @@ -11,7 +11,7 @@ import sys, os, importlib, time from PyQt5.Qt import QIcon from calibre.constants import islinux, iswindows -from calibre.gui2 import Application, ORG_NAME, APP_UID, setup_gui_option_parser, detach_gui, decouple +from calibre.gui2 import Application, ORG_NAME, APP_UID, setup_gui_option_parser, decouple from calibre.ptempfile import reset_base_dir from calibre.utils.config import OptionParser @@ -51,8 +51,6 @@ def _run(args, notify=None): parser = option_parser() opts, args = parser.parse_args(args) - if getattr(opts, 'detach', False): - detach_gui() decouple('edit-book-') override = 'calibre-edit-book' if islinux else None app = Application(args, override_program_name=override) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 181dd44e9f..1f616d007d 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -15,7 +15,7 @@ from calibre.gui2.viewer.toc import TOC from calibre.gui2.widgets import ProgressIndicator from calibre.gui2 import ( Application, ORG_NAME, APP_UID, choose_files, info_dialog, error_dialog, - open_url, setup_gui_option_parser, detach_gui) + open_url, setup_gui_option_parser) from calibre.ebooks.oeb.iterator.book import EbookIterator from calibre.ebooks import DRMError from calibre.constants import islinux, filesystem_encoding @@ -991,8 +991,6 @@ def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) - if getattr(opts, 'detach', False): - detach_gui() open_at = float(opts.open_at.replace(',', '.')) if opts.open_at else None override = 'calibre-ebook-viewer' if islinux else None app = Application(args, override_program_name=override) diff --git a/src/calibre/gui_launch.py b/src/calibre/gui_launch.py new file mode 100644 index 0000000000..9ddb6fb3d8 --- /dev/null +++ b/src/calibre/gui_launch.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +import os, sys + +# For some reason Qt 5 crashes on some linux systems if the fork() is done +# after the Qt modules are loaded in calibre.gui2. We also cannot do a fork() +# while python is importing a module. So we use this simple launcher module to +# launch all the GUI apps, forking before Qt is loaded and not during a +# python import. + +def do_detach(fork=True, setsid=True, redirect=True): + if fork: + # Detach from the controlling process. + if os.fork() != 0: + raise SystemExit(0) + if setsid: + os.setsid() + if redirect: + from calibre.constants import plugins + try: + plugins['speedup'][0].detach(os.devnull) + except AttributeError: + pass # people running from source without updated binaries + +def detach_gui(): + from calibre.constants import islinux, isbsd, DEBUG + if (islinux or isbsd) and not DEBUG and '--detach' in sys.argv: + do_detach() + + +def calibre(): + detach_gui() + from calibre.gui2.main import main + main() + +def ebook_viewer(): + detach_gui() + from calibre.gui2.viewer.main import main + main() + +def ebook_edit(): + detach_gui() + from calibre.gui2.tweak_book.main import main + main() + diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 785db7a035..710d75e8e3 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -34,11 +34,11 @@ entry_points = { 'calibre-smtp = calibre.utils.smtp:main', ], 'gui_scripts' : [ - __appname__+' = calibre.gui2.main:main', + __appname__+' = calibre.gui_launch:calibre', 'lrfviewer = calibre.gui2.lrf_renderer.main:main', - 'ebook-viewer = calibre.gui2.viewer.main:main', - 'ebook-edit = calibre.gui2.tweak_book.main:main', - ], + 'ebook-viewer = calibre.gui_launch:ebook_viewer', + 'ebook-edit = calibre.gui_launch:ebook_edit', + ], } class PreserveMIMEDefaults(object):