[Core] Defer save state function to separate thread

With large amounts of torrents, saving the state file becomes
a performance bottleneck, mainly due to the required processing
in pickle.dump. When run in the main thread, the server will
hang and be unresponsive for a significant time.

Solve this issue by running the save state job in a separate thread.
This commit is contained in:
bendikro 2015-11-23 14:01:34 +01:00 committed by Calum Lind
parent e632ca4418
commit d13fca251e
2 changed files with 28 additions and 11 deletions

View File

@ -2,7 +2,7 @@
* libtorrent (rasterbar) >= 0.16.7
* python >= 2.6
* setuptools
* twisted >= 8.1
* twisted >= 11.1
* pyopenssl
* pyxdg
* chardet

View File

@ -16,7 +16,7 @@ import os
import shutil
import time
from twisted.internet import reactor
from twisted.internet import defer, reactor, threads
from twisted.internet.defer import Deferred, DeferredList
from twisted.internet.task import LoopingCall
@ -104,6 +104,7 @@ class TorrentManager(component.Component):
# Create the torrents dict { torrent_id: Torrent }
self.torrents = {}
self.queued_torrents = set()
self.is_saving_state = False
# This is a map of torrent_ids to Deferreds used to track needed resume data.
# The Deferreds will be completed when resume data has been saved.
@ -194,6 +195,7 @@ class TorrentManager(component.Component):
self.save_resume_data_timer.start(190, False)
self.prev_status_cleanup_loop.start(10)
@defer.inlineCallbacks
def stop(self):
# Stop timers
if self.save_state_timer.running:
@ -206,18 +208,14 @@ class TorrentManager(component.Component):
self.prev_status_cleanup_loop.stop()
# Save state on shutdown
self.save_state()
yield self.save_state()
self.session.pause()
def remove_temp_file(result):
"""Remove the temp_file to signify successfully saved state"""
if result and os.path.isfile(self.temp_file):
os.remove(self.temp_file)
d = self.save_resume_data(flush_disk_cache=True)
d.addCallback(remove_temp_file)
return d
result = yield self.save_resume_data(flush_disk_cache=True)
# Remove the temp_file to signify successfully saved state
if result and os.path.isfile(self.temp_file):
os.remove(self.temp_file)
def update(self):
for torrent_id, torrent in self.torrents.items():
@ -647,6 +645,25 @@ class TorrentManager(component.Component):
return state
def save_state(self):
"""
Run the save state task in a separate thread to avoid blocking main thread.
If a save task is already running, this call is ignored.
"""
if self.is_saving_state:
return defer.succeed(None)
self.is_saving_state = True
d = threads.deferToThread(self._save_state)
def on_state_saved(arg):
self.is_saving_state = False
if self.save_state_timer.running:
self.save_state_timer.reset()
d.addBoth(on_state_saved)
return d
def _save_state(self):
"""Save the state of the TorrentManager to the torrents.state file."""
state = self.create_state()
if not state.torrents: