[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:
parent
e632ca4418
commit
d13fca251e
2
DEPENDS
2
DEPENDS
|
@ -2,7 +2,7 @@
|
|||
* libtorrent (rasterbar) >= 0.16.7
|
||||
* python >= 2.6
|
||||
* setuptools
|
||||
* twisted >= 8.1
|
||||
* twisted >= 11.1
|
||||
* pyopenssl
|
||||
* pyxdg
|
||||
* chardet
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue