Disable automatic garbage collection, instead ensure garbage collection runs only in the GUI thread

This commit is contained in:
Kovid Goyal 2011-03-15 18:39:14 -06:00
parent 428ed899fc
commit e937dccaa3
2 changed files with 63 additions and 13 deletions

View File

@ -1,10 +1,14 @@
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import StringIO, traceback, sys
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\ import StringIO, traceback, sys, gc
QAction, QMenu, QMenuBar, QIcon, pyqtSignal
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.gui2.dialogs.conversion_error import ConversionErrorDialog
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
@ -35,6 +39,53 @@ class DebugWindow(ConversionErrorDialog):
def flush(self): def flush(self):
pass 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): class MainWindow(QMainWindow):
___menu_bar = None ___menu_bar = None
@ -64,19 +115,15 @@ class MainWindow(QMainWindow):
quit_action.setMenuRole(QAction.QuitRole) quit_action.setMenuRole(QAction.QuitRole)
return preferences_action, quit_action 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) QMainWindow.__init__(self, parent)
app = QCoreApplication.instance() if disable_automatic_gc:
if app is not None: self._gc = GarbageCollector(self, debug=False)
self.connect(app, SIGNAL('unixSignal(int)'), self.unix_signal)
if getattr(opts, 'redirect', False): if getattr(opts, 'redirect', False):
self.__console_redirect = DebugWindow(self) self.__console_redirect = DebugWindow(self)
sys.stdout = sys.stderr = self.__console_redirect sys.stdout = sys.stderr = self.__console_redirect
self.__console_redirect.show() self.__console_redirect.show()
def unix_signal(self, signal):
print 'Received signal:', repr(signal)
def unhandled_exception(self, type, value, tb): def unhandled_exception(self, type, value, tb):
if type == KeyboardInterrupt: if type == KeyboardInterrupt:
self.keyboard_interrupt.emit() self.keyboard_interrupt.emit()

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
'''The main GUI''' '''The main GUI'''
import collections, os, sys, textwrap, time import collections, os, sys, textwrap, time, gc
from Queue import Queue, Empty from Queue import Queue, Empty
from threading import Thread from threading import Thread
from PyQt4.Qt import Qt, SIGNAL, QTimer, QHelpEvent, QAction, \ 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): 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.opts = opts
self.device_connected = None self.device_connected = None
self.gui_debug = gui_debug self.gui_debug = gui_debug
@ -298,6 +298,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
raise raise
self.device_manager.set_current_library_uuid(db.library_id) 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: if show_gui and self.gui_debug is not None:
info_dialog(self, _('Debug mode'), '<p>' + info_dialog(self, _('Debug mode'), '<p>' +
_('You have started calibre in debug mode. After you ' _('You have started calibre in debug mode. After you '
@ -399,6 +402,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
elif msg.startswith('refreshdb:'): elif msg.startswith('refreshdb:'):
self.library_view.model().refresh() self.library_view.model().refresh()
self.library_view.model().research() self.library_view.model().research()
self.tags_view.recount()
else: else:
print msg print msg
@ -465,7 +469,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.device_manager.set_current_library_uuid(db.library_id) self.device_manager.set_current_library_uuid(db.library_id)
# Run a garbage collection now so that it does not freeze the # Run a garbage collection now so that it does not freeze the
# interface later # interface later
import gc
gc.collect() gc.collect()