mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-03 15:08:40 +00:00
[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 changed files with 28 additions and 11 deletions
2
DEPENDS
2
DEPENDS
|
@ -2,7 +2,7 @@
|
||||||
* libtorrent (rasterbar) >= 0.16.7
|
* libtorrent (rasterbar) >= 0.16.7
|
||||||
* python >= 2.6
|
* python >= 2.6
|
||||||
* setuptools
|
* setuptools
|
||||||
* twisted >= 8.1
|
* twisted >= 11.1
|
||||||
* pyopenssl
|
* pyopenssl
|
||||||
* pyxdg
|
* pyxdg
|
||||||
* chardet
|
* chardet
|
||||||
|
|
|
@ -16,7 +16,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import defer, reactor, threads
|
||||||
from twisted.internet.defer import Deferred, DeferredList
|
from twisted.internet.defer import Deferred, DeferredList
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ class TorrentManager(component.Component):
|
||||||
# Create the torrents dict { torrent_id: Torrent }
|
# Create the torrents dict { torrent_id: Torrent }
|
||||||
self.torrents = {}
|
self.torrents = {}
|
||||||
self.queued_torrents = set()
|
self.queued_torrents = set()
|
||||||
|
self.is_saving_state = False
|
||||||
|
|
||||||
# This is a map of torrent_ids to Deferreds used to track needed resume data.
|
# 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.
|
# 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.save_resume_data_timer.start(190, False)
|
||||||
self.prev_status_cleanup_loop.start(10)
|
self.prev_status_cleanup_loop.start(10)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
def stop(self):
|
def stop(self):
|
||||||
# Stop timers
|
# Stop timers
|
||||||
if self.save_state_timer.running:
|
if self.save_state_timer.running:
|
||||||
|
@ -206,19 +208,15 @@ class TorrentManager(component.Component):
|
||||||
self.prev_status_cleanup_loop.stop()
|
self.prev_status_cleanup_loop.stop()
|
||||||
|
|
||||||
# Save state on shutdown
|
# Save state on shutdown
|
||||||
self.save_state()
|
yield self.save_state()
|
||||||
|
|
||||||
self.session.pause()
|
self.session.pause()
|
||||||
|
|
||||||
def remove_temp_file(result):
|
result = yield self.save_resume_data(flush_disk_cache=True)
|
||||||
"""Remove the temp_file to signify successfully saved state"""
|
# Remove the temp_file to signify successfully saved state
|
||||||
if result and os.path.isfile(self.temp_file):
|
if result and os.path.isfile(self.temp_file):
|
||||||
os.remove(self.temp_file)
|
os.remove(self.temp_file)
|
||||||
|
|
||||||
d = self.save_resume_data(flush_disk_cache=True)
|
|
||||||
d.addCallback(remove_temp_file)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
for torrent_id, torrent in self.torrents.items():
|
for torrent_id, torrent in self.torrents.items():
|
||||||
# XXX: Should the state check be those that _can_ be stopped at ratio
|
# XXX: Should the state check be those that _can_ be stopped at ratio
|
||||||
|
@ -647,6 +645,25 @@ class TorrentManager(component.Component):
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def save_state(self):
|
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."""
|
"""Save the state of the TorrentManager to the torrents.state file."""
|
||||||
state = self.create_state()
|
state = self.create_state()
|
||||||
if not state.torrents:
|
if not state.torrents:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue