Optimize the single book add/save case by using a spare pool

This commit is contained in:
Kovid Goyal 2014-11-12 13:00:46 +05:30
parent bba5cbf11e
commit db7c7931a3
6 changed files with 26 additions and 30 deletions

View File

@ -159,7 +159,8 @@ class AddAction(InterfaceAction):
def do_add_recursive(self, root, single): def do_add_recursive(self, root, single):
from calibre.gui2.add2 import Adder from calibre.gui2.add2 import Adder
Adder(root, single_book_per_directory=single, db=self.gui.current_db, callback=self._files_added, parent=self.gui) Adder(root, single_book_per_directory=single, db=self.gui.current_db,
callback=self._files_added, parent=self.gui, pool=self.gui.spare_pool())
def add_recursive_single(self, *args): def add_recursive_single(self, *args):
''' '''
@ -362,7 +363,8 @@ class AddAction(InterfaceAction):
if not paths: if not paths:
return return
from calibre.gui2.add2 import Adder from calibre.gui2.add2 import Adder
Adder(paths, db=None if to_device else self.gui.current_db, parent=self.gui, callback=partial(self._files_added, on_card=on_card)) Adder(paths, db=None if to_device else self.gui.current_db,
parent=self.gui, callback=partial(self._files_added, on_card=on_card), pool=self.gui.spare_pool())
def _files_added(self, adder, on_card=None): def _files_added(self, adder, on_card=None):
if adder.items: if adder.items:
@ -485,4 +487,4 @@ class AddAction(InterfaceAction):
if ok_paths: if ok_paths:
from calibre.gui2.add2 import Adder from calibre.gui2.add2 import Adder
callback = partial(self._add_from_device_adder, on_card=None, model=view.model()) callback = partial(self._add_from_device_adder, on_card=None, model=view.model())
Adder(ok_paths, db=None, parent=self.gui, callback=callback) Adder(ok_paths, db=None, parent=self.gui, callback=callback, pool=self.gui.spare_pool())

View File

@ -113,7 +113,7 @@ class SaveToDiskAction(InterfaceAction):
if save_cover is not None: if save_cover is not None:
opts.save_cover = save_cover opts.save_cover = save_cover
book_ids = set(map(self.gui.library_view.model().id, rows)) book_ids = set(map(self.gui.library_view.model().id, rows))
Saver(book_ids, self.gui.current_db, opts, path, parent=self.gui) Saver(book_ids, self.gui.current_db, opts, path, parent=self.gui, pool=self.gui.spare_pool())
else: else:
paths = self.gui.current_view().model().paths(rows) paths = self.gui.current_view().model().paths(rows)
self.gui.device_manager.save_books( self.gui.device_manager.save_books(

View File

@ -55,7 +55,7 @@ class Adder(QObject):
do_one_signal = pyqtSignal() do_one_signal = pyqtSignal()
def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None): def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None, pool=None):
if not validate_source(source, parent): if not validate_source(source, parent):
raise ValueError('Bad source') raise ValueError('Bad source')
QObject.__init__(self, parent) QObject.__init__(self, parent)
@ -64,7 +64,7 @@ class Adder(QObject):
self.add_formats_to_existing = prefs['add_formats_to_existing'] self.add_formats_to_existing = prefs['add_formats_to_existing']
self.do_one_signal.connect(self.tick, type=Qt.QueuedConnection) self.do_one_signal.connect(self.tick, type=Qt.QueuedConnection)
self.tdir = PersistentTemporaryDirectory('_add_books') self.tdir = PersistentTemporaryDirectory('_add_books')
self.pool = None self.pool = pool
self.pd = ProgressDialog(_('Adding books...'), _('Scanning for files...'), min=0, max=0, parent=parent, icon='add_book.png') self.pd = ProgressDialog(_('Adding books...'), _('Scanning for files...'), min=0, max=0, parent=parent, icon='add_book.png')
self.db = getattr(db, 'new_api', None) self.db = getattr(db, 'new_api', None)
if self.db is not None: if self.db is not None:

View File

@ -72,7 +72,7 @@ class Saver(QObject):
do_one_signal = pyqtSignal() do_one_signal = pyqtSignal()
def __init__(self, book_ids, db, opts, root, parent=None): def __init__(self, book_ids, db, opts, root, parent=None, pool=None):
QObject.__init__(self, parent) QObject.__init__(self, parent)
self.db = db.new_api self.db = db.new_api
self.plugboards = self.db.pref('plugboards', {}) self.plugboards = self.db.pref('plugboards', {})
@ -87,7 +87,7 @@ class Saver(QObject):
self.do_one = self.do_one_collect self.do_one = self.do_one_collect
self.ids_to_collect = iter(self.all_book_ids) self.ids_to_collect = iter(self.all_book_ids)
self.tdir = PersistentTemporaryDirectory('_save_to_disk') self.tdir = PersistentTemporaryDirectory('_save_to_disk')
self.pool = None self.pool = pool
self.pd.show() self.pd.show()
self.root, self.opts, self.path_length = sanitize_args(root, opts) self.root, self.opts, self.path_length = sanitize_args(root, opts)

View File

@ -20,10 +20,10 @@ from PyQt5.Qt import (
Qt, QTimer, QAction, QMenu, QIcon, pyqtSignal, QUrl, QFont, QDialog, Qt, QTimer, QAction, QMenu, QIcon, pyqtSignal, QUrl, QFont, QDialog,
QApplication, QSystemTrayIcon) QApplication, QSystemTrayIcon)
from calibre import prints, force_unicode from calibre import prints, force_unicode, detect_ncpus
from calibre.constants import __appname__, isosx, filesystem_encoding, DEBUG from calibre.constants import __appname__, isosx, filesystem_encoding, DEBUG
from calibre.utils.config import prefs, dynamic from calibre.utils.config import prefs, dynamic
from calibre.utils.ipc.server import Server from calibre.utils.ipc.pool import Pool
from calibre.db.legacy import LibraryDatabase from calibre.db.legacy import LibraryDatabase
from calibre.customize.ui import interface_actions, available_store_plugins from calibre.customize.ui import interface_actions, available_store_plugins
from calibre.gui2 import (error_dialog, GetMetadata, open_url, from calibre.gui2 import (error_dialog, GetMetadata, open_url,
@ -213,7 +213,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.preferences_action, self.quit_action = actions self.preferences_action, self.quit_action = actions
self.library_path = library_path self.library_path = library_path
self.content_server = None self.content_server = None
self.spare_servers = [] self._spare_pool = None
self.must_restart_before_config = False self.must_restart_before_config = False
self.listener = Listener(listener) self.listener = Listener(listener)
self.check_messages_timer = QTimer() self.check_messages_timer = QTimer()
@ -312,7 +312,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.alt_esc_action.triggered.connect(self.clear_additional_restriction) self.alt_esc_action.triggered.connect(self.clear_additional_restriction)
# ###################### Start spare job server ######################## # ###################### Start spare job server ########################
QTimer.singleShot(1000, self.add_spare_server) QTimer.singleShot(1000, self.create_spare_pool)
# ###################### Location Manager ######################## # ###################### Location Manager ########################
self.location_manager.location_selected.connect(self.location_selected) self.location_manager.location_selected.connect(self.location_selected)
@ -472,21 +472,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
MainWindow.resizeEvent(self, ev) MainWindow.resizeEvent(self, ev)
self.search.setMaximumWidth(self.width()-150) self.search.setMaximumWidth(self.width()-150)
def add_spare_server(self, *args): def create_spare_pool(self, *args):
self.spare_servers.append(Server(limit=int(config['worker_limit']/2.0))) if self._spare_pool is None:
num = min(detect_ncpus(), int(config['worker_limit']/2.0))
self._spare_pool = Pool(max_workers=num, name='GUIPool')
@property def spare_pool(self):
def spare_server(self): ans, self._spare_pool = self._spare_pool, None
# Because of the use of the property decorator, we're called one QTimer.singleShot(1000, self.create_spare_pool)
# extra time. Ignore. return ans
if not hasattr(self, '__spare_server_property_limiter'):
self.__spare_server_property_limiter = True
return None
try:
QTimer.singleShot(1000, self.add_spare_server)
return self.spare_servers.pop()
except:
pass
def do_proceed(self, func, payload): def do_proceed(self, func, payload):
if callable(func): if callable(func):
@ -893,8 +887,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.listener.close() self.listener.close()
self.job_manager.server.close() self.job_manager.server.close()
self.job_manager.threaded_server.close() self.job_manager.threaded_server.close()
while self.spare_servers:
self.spare_servers.pop().close()
self.device_manager.keep_going = False self.device_manager.keep_going = False
self.auto_adder.stop() self.auto_adder.stop()
mb = self.library_view.model().metadata_backup mb = self.library_view.model().metadata_backup
@ -912,6 +904,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
pass pass
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
if self._spare_pool is not None:
self._spare_pool.shutdown()
from calibre.db.delete_service import shutdown from calibre.db.delete_service import shutdown
shutdown() shutdown()
time.sleep(2) time.sleep(2)

View File

@ -122,12 +122,12 @@ class Pool(Thread):
else: else:
join_with_timeout(self.tracker, timeout) join_with_timeout(self.tracker, timeout)
def shutdown(self): def shutdown(self, wait_time=0.1):
''' Shutdown this pool, terminating all worker process. The pool cannot ''' Shutdown this pool, terminating all worker process. The pool cannot
be used after a shutdown. ''' be used after a shutdown. '''
self.shutting_down = True self.shutting_down = True
self.events.put(None) self.events.put(None)
self.shutdown_workers() self.shutdown_workers(wait_time=wait_time)
def create_worker(self): def create_worker(self):
from calibre.utils.ipc.simple_worker import start_pipe_worker from calibre.utils.ipc.simple_worker import start_pipe_worker