From 20e2ae2f86fd3561123d855921d235c1bce9a631 Mon Sep 17 00:00:00 2001 From: echel0n Date: Sun, 15 Jun 2014 04:08:41 -0700 Subject: [PATCH] Improved tornado async routines and shutdown routines. --- SickBeard.py | 5 +- sickbeard/__init__.py | 18 +++-- sickbeard/webserve.py | 140 ++++++++++++++++++++------------------ sickbeard/webserveInit.py | 29 ++------ 4 files changed, 92 insertions(+), 100 deletions(-) diff --git a/SickBeard.py b/SickBeard.py index a6c7bf28..ad9395d6 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -361,15 +361,12 @@ def main(): 'https_key': sickbeard.HTTPS_KEY, } - # init tornado server - sickbeard.WEBSERVER = webserverInit(options) - # Build from the DB to start with logger.log(u"Loading initial show list") loadShowsFromDB() # start tornado thread - sickbeard.WEBSERVER.start() + webserverInit(options).start() # Fire up all our threads sickbeard.start() diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 70ff8b9b..e12ca08d 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -77,7 +77,6 @@ PIDFILE = '' DAEMON = None NO_RESIZE = False -WEBSERVER = None maintenanceScheduler = None dailySearchScheduler = None @@ -117,7 +116,6 @@ SOCKET_TIMEOUT = None WEB_PORT = None WEB_LOG = None WEB_ROOT = None -WEB_DATA_ROOT = None WEB_USERNAME = None WEB_PASSWORD = None WEB_HOST = None @@ -479,7 +477,7 @@ def initialize(consoleLogging=True): USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, REMOTE_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, \ AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \ ANIME_DEFAULT, NAMING_ANIME, ANIMESUPPORT, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \ - ANIME_SPLIT_HOME, maintenanceScheduler, SCENE_DEFAULT, WEB_DATA_ROOT, WEBSERVER + ANIME_SPLIT_HOME, maintenanceScheduler, SCENE_DEFAULT if __INITIALIZED__: return False @@ -529,7 +527,6 @@ def initialize(consoleLogging=True): WEB_HOST = check_setting_str(CFG, 'General', 'web_host', '0.0.0.0') WEB_IPV6 = bool(check_setting_int(CFG, 'General', 'web_ipv6', 0)) WEB_ROOT = check_setting_str(CFG, 'General', 'web_root', '').rstrip("/") - WEB_DATA_ROOT = os.path.join(PROG_DIR, 'gui/' + GUI_NAME) WEB_LOG = bool(check_setting_int(CFG, 'General', 'web_log', 0)) ENCRYPTION_VERSION = check_setting_int(CFG, 'General', 'encryption_version', 0) WEB_USERNAME = check_setting_str(CFG, 'General', 'web_username', '') @@ -1293,13 +1290,22 @@ def saveAll(): def saveAndShutdown(restart=False): - global WEBSERVER halt() saveAll() # Shutdown tornado - WEBSERVER.shutdown() + logger.log('Shutting down tornado') + + def shutdown(): + try: + IOLoop.current().stop() + except RuntimeError: + pass + except: + logger.log('Failed shutting down the server: %s' % traceback.format_exc(), logger.ERROR) + + IOLoop.current().add_callback(shutdown) if CREATEPID: logger.log(u"Removing pidfile " + str(PIDFILE)) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 7e372ab8..5e273c81 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -31,6 +31,7 @@ import random from Cheetah.Template import Template import sys +from tornado import gen from tornado.httputil import HTTPHeaders from tornado.web import RequestHandler, HTTPError, asynchronous, authenticated import sickbeard @@ -190,7 +191,6 @@ class IndexHandler(RedirectHandler): args[arg] = value[0] return args - @asynchronous def _dispatch(self): """ Load up the requested URL if it matches one of our own methods. @@ -233,15 +233,25 @@ class IndexHandler(RedirectHandler): if self.request.uri != ('/'): raise HTTPError(404) + def get_response(self): + raise gen.Return('hello') + def get_current_user(self): return self.get_secure_cookie("user") @authenticated + @asynchronous + @gen.coroutine def get(self, *args, **kwargs): - return self._dispatch() + resp = yield self.get_response() + self.finish(resp) + + @gen.coroutine + def get_response(self): + raise gen.Return(self._dispatch()) def post(self, *args, **kwargs): - return self._dispatch() + self.finish(self._dispatch()) def robots_txt(self, *args, **kwargs): """ Keep web crawlers out """ @@ -271,10 +281,10 @@ class IndexHandler(RedirectHandler): if ek.ek(os.path.isfile, image_file_name): with file(image_file_name, 'rb') as img: - return self.finish(img.read()) + return img.read() with file(default_image_path, 'rb') as img: - return self.finish(img.read()) + return img.read() def setHomeLayout(self, layout): @@ -400,7 +410,7 @@ class IndexHandler(RedirectHandler): else: t.layout = sickbeard.COMING_EPS_LAYOUT - return self.finish(_munge(t)) + return _munge(t) # Raw iCalendar implementation by Pedro Jose Pereira Vieito (@pvieito). # @@ -580,7 +590,7 @@ class ManageSearches(IndexHandler): t.submenu = ManageMenu() - return self.finish(_munge(t)) + return _munge(t) def forceBacklog(self, *args, **kwargs): @@ -638,7 +648,7 @@ class Manage(IndexHandler): def index(self, *args, **kwargs): t = PageTemplate(file="manage.tmpl") t.submenu = ManageMenu() - return self.finish(_munge(t)) + return _munge(t) def showEpisodeStatuses(self, indexer_id, whichStatus): @@ -680,7 +690,7 @@ class Manage(IndexHandler): # if we have no status then this is as far as we need to go if not status_list: - return self.finish(_munge(t)) + return _munge(t) with db.DBConnection() as myDB: status_results = myDB.select( @@ -706,7 +716,7 @@ class Manage(IndexHandler): t.show_names = show_names t.ep_counts = ep_counts t.sorted_show_ids = sorted_show_ids - return self.finish(_munge(t)) + return _munge(t) def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs): @@ -787,7 +797,7 @@ class Manage(IndexHandler): t.whichSubs = whichSubs if not whichSubs: - return self.finish(_munge(t)) + return _munge(t) with db.DBConnection() as myDB: status_results = myDB.select( @@ -817,7 +827,7 @@ class Manage(IndexHandler): t.show_names = show_names t.ep_counts = ep_counts t.sorted_show_ids = sorted_show_ids - return self.finish(_munge(t)) + return _munge(t) def downloadSubtitleMissed(self, *args, **kwargs): @@ -903,7 +913,7 @@ class Manage(IndexHandler): t.showCats = showCats t.showSQLResults = showSQLResults - return self.finish(_munge(t)) + return _munge(t) def massEdit(self, toEdit=None): @@ -996,7 +1006,7 @@ class Manage(IndexHandler): t.scene_value = last_scene if scene_all_same else None t.root_dir_list = root_dir_list - return self.finish(_munge(t)) + return _munge(t) def massEditSubmit(self, paused=None, anime=None, scene=None, flatten_folders=None, quality_preset=False, @@ -1212,7 +1222,7 @@ class Manage(IndexHandler): else: t.info_download_station = '

To have a better experience please set the Download Station alias as download, you can check this setting in the Synology DSM Control Panel > Application Portal. Make sure you allow DSM to be embedded with iFrames too in Control Panel > DSM Settings > Security.


There is more information about this available here.


' - return self.finish(_munge(t)) + return _munge(t) def failedDownloads(self, limit=100, toRemove=None): @@ -1237,7 +1247,7 @@ class Manage(IndexHandler): t.limit = limit t.submenu = ManageMenu() - return self.finish(_munge(t)) + return _munge(t) class History(IndexHandler): @@ -1309,7 +1319,7 @@ class History(IndexHandler): {'title': 'Trim History', 'path': 'history/trimHistory'}, ] - return self.finish(_munge(t)) + return _munge(t) def clearHistory(self, *args, **kwargs): @@ -1346,7 +1356,7 @@ class ConfigGeneral(IndexHandler): t = PageTemplate(file="config_general.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def saveRootDirs(self, rootDirString=None): @@ -1494,7 +1504,7 @@ class ConfigSearch(IndexHandler): t = PageTemplate(file="config_search.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None, @@ -1579,7 +1589,7 @@ class ConfigPostProcessing(IndexHandler): t = PageTemplate(file="config_postProcessing.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def savePostProcessing(self, naming_pattern=None, naming_multi_ep=None, @@ -1751,7 +1761,7 @@ class ConfigProviders(IndexHandler): def index(self, *args, **kwargs): t = PageTemplate(file="config_providers.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def canAddNewznabProvider(self, name): @@ -2123,7 +2133,7 @@ class ConfigNotifications(IndexHandler): def index(self, *args, **kwargs): t = PageTemplate(file="config_notifications.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, @@ -2326,7 +2336,7 @@ class ConfigSubtitles(IndexHandler): def index(self, *args, **kwargs): t = PageTemplate(file="config_subtitles.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def saveSubtitles(self, use_subtitles=None, subtitles_plugins=None, subtitles_languages=None, subtitles_dir=None, @@ -2386,7 +2396,7 @@ class ConfigAnime(IndexHandler): t = PageTemplate(file="config_anime.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) def saveAnime(self, use_anidb=None, anidb_username=None, anidb_password=None, anidb_use_mylist=None, @@ -2433,7 +2443,7 @@ class Config(IndexHandler): t = PageTemplate(file="config.tmpl") t.submenu = ConfigMenu - return self.finish(_munge(t)) + return _munge(t) # map class names to urls general = ConfigGeneral @@ -2478,7 +2488,7 @@ class HomePostProcess(IndexHandler): t = PageTemplate(file="home_postprocess.tmpl") t.submenu = HomeMenu() - return self.finish(_munge(t)) + return _munge(t) def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None, @@ -2508,7 +2518,7 @@ class HomePostProcess(IndexHandler): return result result = result.replace("\n", "
\n") - self.finish(_genericMessage("Postprocessing results", result)) + _genericMessage("Postprocessing results", result) class NewHomeAddShows(IndexHandler): @@ -2516,7 +2526,7 @@ class NewHomeAddShows(IndexHandler): t = PageTemplate(file="home_addShows.tmpl") t.submenu = HomeMenu() - return self.finish(_munge(t)) + return _munge(t) def getIndexerLanguages(self, *args, **kwargs): @@ -2646,7 +2656,7 @@ class NewHomeAddShows(IndexHandler): t.dirList = dir_list - return self.finish(_munge(t)) + return _munge(t) def newShow(self, show_to_add=None, other_shows=None): @@ -2690,7 +2700,7 @@ class NewHomeAddShows(IndexHandler): t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT) t.indexers = sickbeard.indexerApi().indexers - return self.finish(_munge(t)) + return _munge(t) def existingShows(self, *args, **kwargs): @@ -2700,7 +2710,7 @@ class NewHomeAddShows(IndexHandler): t = PageTemplate(file="home_addExistingShow.tmpl") t.submenu = HomeMenu() - return self.finish(_munge(t)) + return _munge(t) def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None, @@ -2902,7 +2912,7 @@ class ErrorLogs(IndexHandler): t = PageTemplate(file="errorlogs.tmpl") t.submenu = ErrorLogsMenu - return self.finish(_munge(t)) + return _munge(t) def clearerrors(self, *args, **kwargs): @@ -2961,7 +2971,7 @@ class ErrorLogs(IndexHandler): t.logLines = result t.minLevel = minLevel - return self.finish(_munge(t)) + return _munge(t) class Home(IndexHandler): @@ -2994,7 +3004,7 @@ class Home(IndexHandler): t.showlists = [["Shows", sickbeard.showList]] t.submenu = HomeMenu() - return self.finish(_munge(t)) + return _munge(t) addShows = NewHomeAddShows postprocess = HomePostProcess @@ -3280,7 +3290,7 @@ class Home(IndexHandler): title = "Shutting down" message = "SickRage is shutting down..." - return self.finish(_genericMessage(title, message)) + return _genericMessage(title, message) def restart(self, pid=None): @@ -3293,7 +3303,7 @@ class Home(IndexHandler): # do a soft restart threading.Timer(2, sickbeard.invoke_restart, [False]).start() - return self.finish(_munge(t)) + return _munge(t) def update(self, pid=None): @@ -3307,7 +3317,7 @@ class Home(IndexHandler): # do a hard restart threading.Timer(2, sickbeard.invoke_restart, [False]).start() t = PageTemplate(file="restart_bare.tmpl") - return self.finish(_munge(t)) + return _munge(t) else: return self.finish(_genericMessage("Update Failed", "Update wasn't successful, not restarting. Check your log for more information.")) @@ -3316,12 +3326,12 @@ class Home(IndexHandler): def displayShow(self, show=None): if show is None: - return self.finish(_genericMessage("Error", "Invalid show ID")) + return _genericMessage("Error", "Invalid show ID") else: showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if showObj is None: - return self.finish(_genericMessage("Error", "Show not in show list")) + return _genericMessage("Error", "Show not in show list") showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid) @@ -3440,7 +3450,7 @@ class Home(IndexHandler): t.scene_absolute_numbering = get_scene_absolute_numbering_for_show(indexerid, indexer) t.xem_absolute_numbering = get_xem_absolute_numbering_for_show(indexerid, indexer) - return self.finish(_munge(t)) + return _munge(t) def plotDetails(self, show, season, episode): @@ -3475,7 +3485,7 @@ class Home(IndexHandler): if directCall: return [errString] else: - return self.finish(_genericMessage("Error", errString)) + return _genericMessage("Error", errString) showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) @@ -3484,7 +3494,7 @@ class Home(IndexHandler): if directCall: return [errString] else: - return self.finish(_genericMessage("Error", errString)) + return _genericMessage("Error", errString) showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid) @@ -3520,7 +3530,7 @@ class Home(IndexHandler): t.scene_exceptions = get_scene_exceptions(showObj.indexerid) - return self.finish(_munge(t)) + return _munge(t) flatten_folders = config.checkbox_to_value(flatten_folders) logger.log(u"flatten folders: " + str(flatten_folders)) @@ -3725,16 +3735,16 @@ class Home(IndexHandler): def deleteShow(self, show=None): if show is None: - return self.finish(_genericMessage("Error", "Invalid show ID")) + return _genericMessage("Error", "Invalid show ID") showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if showObj is None: - return self.finish(_genericMessage("Error", "Unable to find the specified show")) + return _genericMessage("Error", "Unable to find the specified show") if sickbeard.showQueueScheduler.action.isBeingAdded( showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable - return self.finish(_genericMessage("Error", "Shows can't be deleted while they're being added or updated.")) + return _genericMessage("Error", "Shows can't be deleted while they're being added or updated.") showObj.deleteShow() @@ -3745,12 +3755,12 @@ class Home(IndexHandler): def refreshShow(self, show=None): if show is None: - return self.finish(_genericMessage("Error", "Invalid show ID")) + return _genericMessage("Error", "Invalid show ID") showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if showObj is None: - return self.finish(_genericMessage("Error", "Unable to find the specified show")) + return _genericMessage("Error", "Unable to find the specified show") # force the update from the DB try: @@ -3767,12 +3777,12 @@ class Home(IndexHandler): def updateShow(self, show=None, force=0): if show is None: - return self.finish(_genericMessage("Error", "Invalid show ID")) + return _genericMessage("Error", "Invalid show ID") showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if showObj is None: - return self.finish(_genericMessage("Error", "Unable to find the specified show")) + return _genericMessage("Error", "Unable to find the specified show") # force the update try: @@ -3790,12 +3800,12 @@ class Home(IndexHandler): def subtitleShow(self, show=None, force=0): if show is None: - return self.finish(_genericMessage("Error", "Invalid show ID")) + return _genericMessage("Error", "Invalid show ID") showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if showObj is None: - return self.finish(_genericMessage("Error", "Unable to find the specified show")) + return _genericMessage("Error", "Unable to find the specified show") # search and download subtitles sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force)) # @UndefinedVariable @@ -3838,7 +3848,7 @@ class Home(IndexHandler): ui.notifications.error('Error', errMsg) return json.dumps({'result': 'error'}) else: - return self.finish(_genericMessage("Error", errMsg)) + return _genericMessage("Error", errMsg) if not statusStrings.has_key(int(status)): errMsg = "Invalid status" @@ -3846,7 +3856,7 @@ class Home(IndexHandler): ui.notifications.error('Error', errMsg) return json.dumps({'result': 'error'}) else: - return self.finish(_genericMessage("Error", errMsg)) + return _genericMessage("Error", errMsg) showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) @@ -3856,7 +3866,7 @@ class Home(IndexHandler): ui.notifications.error('Error', errMsg) return json.dumps({'result': 'error'}) else: - return self.finish(_genericMessage("Error", errMsg)) + return _genericMessage("Error", errMsg) segment = {} if eps is not None: @@ -3871,7 +3881,7 @@ class Home(IndexHandler): epObj = showObj.getEpisode(int(epInfo[0]), int(epInfo[1])) if epObj is None: - return self.finish(_genericMessage("Error", "Episode couldn't be retrieved")) + return _genericMessage("Error", "Episode couldn't be retrieved") if int(status) in [WANTED, FAILED]: # figure out what episodes are wanted so we can backlog them @@ -3947,17 +3957,17 @@ class Home(IndexHandler): def testRename(self, show=None): if show is None: - return self.finish(_genericMessage("Error", "You must specify a show")) + return _genericMessage("Error", "You must specify a show") showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if showObj is None: - return self.finish(_genericMessage("Error", "Show not in show list")) + return _genericMessage("Error", "Show not in show list") try: show_loc = showObj.location # @UnusedVariable except exceptions.ShowDirNotFoundException: - return self.finish(_genericMessage("Error", "Can't rename episodes when the show dir is missing.")) + return _genericMessage("Error", "Can't rename episodes when the show dir is missing.") ep_obj_rename_list = [] @@ -3987,25 +3997,25 @@ class Home(IndexHandler): t.ep_obj_list = ep_obj_rename_list t.show = showObj - return self.finish(_munge(t)) + return _munge(t) def doRename(self, show=None, eps=None): if show is None or eps is None: errMsg = "You must specify a show and at least one episode" - return self.finish(_genericMessage("Error", errMsg)) + return _genericMessage("Error", errMsg) show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) if show_obj is None: errMsg = "Error", "Show not in show list" - return self.finish(_genericMessage("Error", errMsg)) + return _genericMessage("Error", errMsg) try: show_loc = show_obj.location # @UnusedVariable except exceptions.ShowDirNotFoundException: - return self.finish(_genericMessage("Error", "Can't rename episodes when the show dir is missing.")) + return _genericMessage("Error", "Can't rename episodes when the show dir is missing.") if eps is None: self.redirect("/home/displayShow?show=" + show) @@ -4210,7 +4220,7 @@ class UI(IndexHandler): ui.notifications.message('Test 1', 'This is test number 1') ui.notifications.error('Test 2', 'This is test number 2') - self.finish("ok") + "ok" def get_messages(self, *args, **kwargs): @@ -4222,4 +4232,4 @@ class UI(IndexHandler): 'type': cur_notification.type} cur_notification_num += 1 - self.finish(json.dumps(messages)) + json.dumps(messages) diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index 02ee9b09..fbd59ee4 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -5,13 +5,12 @@ import traceback import datetime import sickbeard import webserve -from sickbeard.exceptions import ex + from sickbeard import logger from sickbeard.helpers import create_https_certificates from tornado.web import Application, StaticFileHandler, RedirectHandler, HTTPError from tornado.httpserver import HTTPServer -from tornado.ioloop import IOLoop, PeriodicCallback - +from tornado.ioloop import IOLoop class MultiStaticFileHandler(StaticFileHandler): def initialize(self, paths, default_filename=None): @@ -152,25 +151,5 @@ class webserverInit(): def start(self): if self.thread == None or not self.thread.isAlive(): - self.thread = threading.Thread(None, IOLoop.current().start, 'TORNADO') - self.thread.start() - - def shutdown(self): - - if self.thread: - logger.log('Shutting down tornado') - - # stop tornado io loop - IOLoop.instance().stop() - - # stop tornado thread - try: - self.thread.join(10) - except: - pass - - # stop tornado http server - self.server.stop() - - # remove thread object - self.thread = None \ No newline at end of file + self.thread = threading.Thread(target=IOLoop.current().start) + self.thread.start() \ No newline at end of file