From e937dccaa37118165c09336e57909b3164af8ddc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Mar 2011 18:39:14 -0600 Subject: [PATCH] Disable automatic garbage collection, instead ensure garbage collection runs only in the GUI thread --- src/calibre/gui2/main_window.py | 67 ++++++++++++++++++++++++++++----- src/calibre/gui2/ui.py | 9 +++-- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py index e068e851c2..ec58dd3856 100644 --- a/src/calibre/gui2/main_window.py +++ b/src/calibre/gui2/main_window.py @@ -1,10 +1,14 @@ +from __future__ import (unicode_literals, division, absolute_import, + print_function) + __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import StringIO, traceback, sys -from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\ - QAction, QMenu, QMenuBar, QIcon, pyqtSignal +import StringIO, traceback, sys, gc + +from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QTimer, \ + QAction, QMenu, QMenuBar, QIcon, pyqtSignal, QObject from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.utils.config import OptionParser from calibre.gui2 import error_dialog @@ -35,6 +39,53 @@ class DebugWindow(ConversionErrorDialog): def flush(self): pass +class GarbageCollector(QObject): + + ''' + Disable automatic garbage collection and instead collect manually + every INTERVAL milliseconds. + + This is done to ensure that garbage collection only happens in the GUI + thread, as otherwise Qt can crash. + ''' + + INTERVAL = 5000 + + def __init__(self, parent, debug=False): + QObject.__init__(self, parent) + self.debug = debug + + self.timer = QTimer(self) + self.timer.timeout.connect(self.check) + + self.threshold = gc.get_threshold() + gc.disable() + self.timer.start(self.INTERVAL) + #gc.set_debug(gc.DEBUG_SAVEALL) + + def check(self): + #return self.debug_cycles() + l0, l1, l2 = gc.get_count() + if self.debug: + print ('gc_check called:', l0, l1, l2) + if l0 > self.threshold[0]: + num = gc.collect(0) + if self.debug: + print ('collecting gen 0, found:', num, 'unreachable') + if l1 > self.threshold[1]: + num = gc.collect(1) + if self.debug: + print ('collecting gen 1, found:', num, 'unreachable') + if l2 > self.threshold[2]: + num = gc.collect(2) + if self.debug: + print ('collecting gen 2, found:', num, 'unreachable') + + def debug_cycles(self): + gc.collect() + for obj in gc.garbage: + print (obj, repr(obj), type(obj)) + class MainWindow(QMainWindow): ___menu_bar = None @@ -64,19 +115,15 @@ class MainWindow(QMainWindow): quit_action.setMenuRole(QAction.QuitRole) return preferences_action, quit_action - def __init__(self, opts, parent=None): + def __init__(self, opts, parent=None, disable_automatic_gc=False): QMainWindow.__init__(self, parent) - app = QCoreApplication.instance() - if app is not None: - self.connect(app, SIGNAL('unixSignal(int)'), self.unix_signal) + if disable_automatic_gc: + self._gc = GarbageCollector(self, debug=False) if getattr(opts, 'redirect', False): self.__console_redirect = DebugWindow(self) sys.stdout = sys.stderr = self.__console_redirect self.__console_redirect.show() - def unix_signal(self, signal): - print 'Received signal:', repr(signal) - def unhandled_exception(self, type, value, tb): if type == KeyboardInterrupt: self.keyboard_interrupt.emit() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 6766635789..4af8c1ea54 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en' '''The main GUI''' -import collections, os, sys, textwrap, time +import collections, os, sys, textwrap, time, gc from Queue import Queue, Empty from threading import Thread from PyQt4.Qt import Qt, SIGNAL, QTimer, QHelpEvent, QAction, \ @@ -95,7 +95,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def __init__(self, opts, parent=None, gui_debug=None): - MainWindow.__init__(self, opts, parent) + MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True) self.opts = opts self.device_connected = None self.gui_debug = gui_debug @@ -298,6 +298,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ raise self.device_manager.set_current_library_uuid(db.library_id) + # Collect cycles now + gc.collect() + if show_gui and self.gui_debug is not None: info_dialog(self, _('Debug mode'), '

' + _('You have started calibre in debug mode. After you ' @@ -399,6 +402,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ elif msg.startswith('refreshdb:'): self.library_view.model().refresh() self.library_view.model().research() + self.tags_view.recount() else: print msg @@ -465,7 +469,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.device_manager.set_current_library_uuid(db.library_id) # Run a garbage collection now so that it does not freeze the # interface later - import gc gc.collect()