1
0
mirror of https://github.com/moparisthebest/SickRage synced 2024-12-13 03:22:22 -05:00

Improved startup/shutdown of tornado.

Fixed issues with notifications related to tornado.
This commit is contained in:
echel0n 2014-06-15 00:16:55 -07:00
parent 04681b3297
commit abff43f568
5 changed files with 135 additions and 101 deletions

View File

@ -361,12 +361,8 @@ def main():
'https_key': sickbeard.HTTPS_KEY,
}
# init tornado web server
sickbeard.webserveInitScheduler = sickbeard.scheduler.Scheduler(webserverInit(options),
cycleTime=datetime.timedelta(seconds=3),
threadName="TORNADO",
silent=True,
runImmediately=True)
# init tornado server
sickbeard.WEBSERVER = webserverInit(options)
# Build from the DB to start with
logger.log(u"Loading initial show list")
@ -375,6 +371,9 @@ def main():
# Fire up all our threads
sickbeard.start()
# start tornado thread
sickbeard.WEBSERVER.thread.start()
# Launch browser if we're supposed to
if sickbeard.LAUNCH_BROWSER and not noLaunch and not sickbeard.DAEMON:
sickbeard.launchBrowser(startPort)

View File

@ -29,7 +29,7 @@ from urllib2 import getproxies
from threading import Lock
# apparently py2exe won't build these unless they're imported somewhere
from sickbeard import providers, metadata, config
from sickbeard import providers, metadata, config, webserveInit
from sickbeard.providers.generic import GenericProvider
from providers import ezrss, tvtorrents, btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, \
omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, nextgen, speedcd, nyaatorrents, fanzub
@ -77,6 +77,7 @@ PIDFILE = ''
DAEMON = None
NO_RESIZE = False
WEBSERVER = None
maintenanceScheduler = None
dailySearchScheduler = None
@ -89,7 +90,6 @@ properFinderScheduler = None
autoPostProcesserScheduler = None
subtitlesFinderScheduler = None
traktWatchListCheckerScheduler = None
webserveInitScheduler = None
showList = None
loadingShowList = None
@ -479,7 +479,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, webserveInitScheduler
ANIME_SPLIT_HOME, maintenanceScheduler, SCENE_DEFAULT, WEB_DATA_ROOT, WEBSERVER
if __INITIALIZED__:
return False
@ -1119,15 +1119,12 @@ def start():
showUpdateScheduler, versionCheckScheduler, showQueueScheduler, \
properFinderScheduler, autoPostProcesserScheduler, searchQueueScheduler, \
subtitlesFinderScheduler, USE_SUBTITLES,traktWatchListCheckerScheduler, \
dailySearchScheduler, webserveInitScheduler, started
dailySearchScheduler, started
with INIT_LOCK:
if __INITIALIZED__:
# start tornado web server
webserveInitScheduler.thread.start()
# start the maintenance scheduler
maintenanceScheduler.thread.start()
logger.log(u"Performing initial maintenance tasks, please wait ...")
@ -1173,7 +1170,7 @@ def halt():
showUpdateScheduler, versionCheckScheduler, showQueueScheduler, \
properFinderScheduler, autoPostProcesserScheduler, searchQueueScheduler, \
subtitlesFinderScheduler, traktWatchListCheckerScheduler, \
dailySearchScheduler, webserveInitScheduler, started
dailySearchScheduler, started
with INIT_LOCK:
@ -1183,13 +1180,6 @@ def halt():
# abort all the threads
webserveInitScheduler.about = True
logger.log(u"Waiting for the TORNADO thread to exit")
try:
webserveInitScheduler.thread.join(10)
except:
pass
maintenanceScheduler.abort = True
logger.log(u"Waiting for the MAINTENANCE scheduler thread to exit")
try:
@ -1197,13 +1187,6 @@ def halt():
except:
pass
dailySearchScheduler.abort = True
logger.log(u"Waiting for the DAILYSEARCHER thread to exit")
try:
dailySearchScheduler.thread.join(10)
except:
pass
backlogSearchScheduler.abort = True
logger.log(u"Waiting for the BACKLOG thread to exit")
try:
@ -1310,16 +1293,12 @@ def saveAll():
def saveAndShutdown(restart=False):
global WEBSERVER
halt()
saveAll()
logger.log(u"Killing tornado")
try:
IOLoop.current().stop()
except RuntimeError:
pass
except:
logger.log('Failed shutting down the server: %s' % traceback.format_exc(), logger.ERROR)
IOLoop.instance().add_callback(WEBSERVER.shutdown)
if CREATEPID:
logger.log(u"Removing pidfile " + str(PIDFILE))

View File

@ -163,7 +163,7 @@ def change_AUTOPOSTPROCESSER_FREQUENCY(freq):
if sickbeard.AUTOPOSTPROCESSER_FREQUENCY < sickbeard.MIN_AUTOPOSTPROCESSER_FREQUENCY:
sickbeard.AUTOPOSTPROCESSER_FREQUENCY = sickbeard.MIN_AUTOPOSTPROCESSER_FREQUENCY
sickbeard.autoPostProcessorScheduler.cycleTime = datetime.timedelta(minutes=sickbeard.AUTOPOSTPROCESSER_FREQUENCY)
sickbeard.autoPostProcesserScheduler.cycleTime = datetime.timedelta(minutes=sickbeard.AUTOPOSTPROCESSER_FREQUENCY)
def change_DAILYSEARCH_FREQUENCY(freq):
sickbeard.DAILYSEARCH_FREQUENCY = to_int(freq, default=sickbeard.DEFAULT_DAILYSEARCH_FREQUENCY)

View File

@ -190,6 +190,7 @@ 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.
@ -235,12 +236,10 @@ class IndexHandler(RedirectHandler):
def get_current_user(self):
return self.get_secure_cookie("user")
@asynchronous
@authenticated
def get(self, *args, **kwargs):
return self._dispatch()
@asynchronous
def post(self, *args, **kwargs):
return self._dispatch()
@ -1065,7 +1064,7 @@ class Manage(IndexHandler):
exceptions_list = []
curErrors += Home().editShow(curShow, new_show_dir, anyQualities, bestQualities, exceptions_list,
curErrors += self.editShow(curShow, new_show_dir, anyQualities, bestQualities, exceptions_list,
new_flatten_folders, new_paused, subtitles=new_subtitles, anime=new_anime,
scene=new_scene, directCall=True)
@ -1602,9 +1601,9 @@ class ConfigPostProcessing(IndexHandler):
config.change_AUTOPOSTPROCESSER_FREQUENCY(autopostprocesser_frequency)
if sickbeard.PROCESS_AUTOMATICALLY:
sickbeard.autoPostProcessorScheduler.silent = False
sickbeard.autoPostProcesserScheduler.silent = False
else:
sickbeard.autoPostProcessorScheduler.silent = True
sickbeard.autoPostProcesserScheduler.silent = True
if unpack:
if self.isRarSupported() != 'not supported':
@ -3281,8 +3280,7 @@ class Home(IndexHandler):
title = "Shutting down"
message = "SickRage is shutting down..."
return _genericMessage(title, message)
return self.finish(_genericMessage(title, message))
def restart(self, pid=None):
@ -3311,8 +3309,8 @@ class Home(IndexHandler):
t = PageTemplate(file="restart_bare.tmpl")
return self.finish(_munge(t))
else:
return _genericMessage("Update Failed",
"Update wasn't successful, not restarting. Check your log for more information.")
return self.finish(_genericMessage("Update Failed",
"Update wasn't successful, not restarting. Check your log for more information."))
def displayShow(self, show=None):
@ -3477,7 +3475,7 @@ class Home(IndexHandler):
if directCall:
return [errString]
else:
return _genericMessage("Error", errString)
return self.finish(_genericMessage("Error", errString))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
@ -3486,7 +3484,7 @@ class Home(IndexHandler):
if directCall:
return [errString]
else:
return _genericMessage("Error", errString)
return self.finish(_genericMessage("Error", errString))
showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid)
@ -3727,16 +3725,16 @@ class Home(IndexHandler):
def deleteShow(self, show=None):
if show is None:
return _genericMessage("Error", "Invalid show ID")
return self.finish(_genericMessage("Error", "Invalid show ID"))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
if showObj is None:
return _genericMessage("Error", "Unable to find the specified show")
return self.finish(_genericMessage("Error", "Unable to find the specified show"))
if sickbeard.showQueueScheduler.action.isBeingAdded(
showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable
return _genericMessage("Error", "Shows can't be deleted while they're being added or updated.")
return self.finish(_genericMessage("Error", "Shows can't be deleted while they're being added or updated."))
showObj.deleteShow()
@ -3747,12 +3745,12 @@ class Home(IndexHandler):
def refreshShow(self, show=None):
if show is None:
return _genericMessage("Error", "Invalid show ID")
return self.finish(_genericMessage("Error", "Invalid show ID"))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
if showObj is None:
return _genericMessage("Error", "Unable to find the specified show")
return self.finish(_genericMessage("Error", "Unable to find the specified show"))
# force the update from the DB
try:
@ -3769,12 +3767,12 @@ class Home(IndexHandler):
def updateShow(self, show=None, force=0):
if show is None:
return _genericMessage("Error", "Invalid show ID")
return self.finish(_genericMessage("Error", "Invalid show ID"))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
if showObj is None:
return _genericMessage("Error", "Unable to find the specified show")
return self.finish(_genericMessage("Error", "Unable to find the specified show"))
# force the update
try:
@ -3792,12 +3790,12 @@ class Home(IndexHandler):
def subtitleShow(self, show=None, force=0):
if show is None:
return _genericMessage("Error", "Invalid show ID")
return self.finish(_genericMessage("Error", "Invalid show ID"))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
if showObj is None:
return _genericMessage("Error", "Unable to find the specified show")
return self.finish(_genericMessage("Error", "Unable to find the specified show"))
# search and download subtitles
sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force)) # @UndefinedVariable
@ -3840,7 +3838,7 @@ class Home(IndexHandler):
ui.notifications.error('Error', errMsg)
return json.dumps({'result': 'error'})
else:
return _genericMessage("Error", errMsg)
return self.finish(_genericMessage("Error", errMsg))
if not statusStrings.has_key(int(status)):
errMsg = "Invalid status"
@ -3848,7 +3846,7 @@ class Home(IndexHandler):
ui.notifications.error('Error', errMsg)
return json.dumps({'result': 'error'})
else:
return _genericMessage("Error", errMsg)
return self.finish(_genericMessage("Error", errMsg))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
@ -3858,7 +3856,7 @@ class Home(IndexHandler):
ui.notifications.error('Error', errMsg)
return json.dumps({'result': 'error'})
else:
return _genericMessage("Error", errMsg)
return self.finish(_genericMessage("Error", errMsg))
segment = {}
if eps is not None:
@ -3873,7 +3871,7 @@ class Home(IndexHandler):
epObj = showObj.getEpisode(int(epInfo[0]), int(epInfo[1]))
if epObj is None:
return _genericMessage("Error", "Episode couldn't be retrieved")
return self.finish(_genericMessage("Error", "Episode couldn't be retrieved"))
if int(status) in [WANTED, FAILED]:
# figure out what episodes are wanted so we can backlog them
@ -3949,17 +3947,17 @@ class Home(IndexHandler):
def testRename(self, show=None):
if show is None:
return _genericMessage("Error", "You must specify a show")
return self.finish(_genericMessage("Error", "You must specify a show"))
showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
if showObj is None:
return _genericMessage("Error", "Show not in show list")
return self.finish(_genericMessage("Error", "Show not in show list"))
try:
show_loc = showObj.location # @UnusedVariable
except exceptions.ShowDirNotFoundException:
return _genericMessage("Error", "Can't rename episodes when the show dir is missing.")
return self.finish(_genericMessage("Error", "Can't rename episodes when the show dir is missing."))
ep_obj_rename_list = []
@ -3996,18 +3994,18 @@ class Home(IndexHandler):
if show is None or eps is None:
errMsg = "You must specify a show and at least one episode"
return _genericMessage("Error", errMsg)
return self.finish(_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 _genericMessage("Error", errMsg)
return self.finish(_genericMessage("Error", errMsg))
try:
show_loc = show_obj.location # @UnusedVariable
except exceptions.ShowDirNotFoundException:
return _genericMessage("Error", "Can't rename episodes when the show dir is missing.")
return self.finish(_genericMessage("Error", "Can't rename episodes when the show dir is missing."))
if eps is None:
self.redirect("/home/displayShow?show=" + show)

View File

@ -1,11 +1,17 @@
import os
import threading
import time
import traceback
import datetime
import sickbeard
import webserve
import tornado.httpserver
import tornado.ioloop
from sickbeard.exceptions import ex
from sickbeard import logger
from sickbeard.helpers import create_https_certificates
from tornado.web import Application, StaticFileHandler, HTTPError, RedirectHandler
from tornado.web import Application, StaticFileHandler, RedirectHandler, HTTPError
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
class MultiStaticFileHandler(StaticFileHandler):
def initialize(self, paths, default_filename=None):
@ -27,17 +33,27 @@ class MultiStaticFileHandler(StaticFileHandler):
# Oops file not found anywhere!
raise HTTPError(404)
class webserverInit():
def __init__(self, options):
def __init__(self, options, cycleTime=datetime.timedelta(seconds=3)):
self.amActive = False
options.setdefault('port', 8081)
options.setdefault('host', '0.0.0.0')
options.setdefault('log_dir', None)
options.setdefault('username', '')
options.setdefault('password', '')
options.setdefault('web_root', '/')
assert isinstance(options['port'], int)
assert 'data_root' in options
self.lastRun = datetime.datetime.fromordinal(1)
self.cycleTime = cycleTime
self.abort = False
self.server = None
self.thread = None
self.options = options
self.options.setdefault('port', 8081)
self.options.setdefault('host', '0.0.0.0')
self.options.setdefault('log_dir', None)
self.options.setdefault('username', '')
self.options.setdefault('password', '')
self.options.setdefault('web_root', '/')
assert isinstance(self.options['port'], int)
assert 'data_root' in self.options
def http_error_401_hander(status, message, traceback, version):
""" Custom handler for 401 error """
@ -72,12 +88,12 @@ class webserverInit():
<br/>
</body>
</html>
''' % options['web_root']
''' % self.options['web_root']
# tornado setup
enable_https = options['enable_https']
https_cert = options['https_cert']
https_key = options['https_key']
enable_https = self.options['enable_https']
https_cert = self.options['https_cert']
https_key = self.options['https_key']
if enable_https:
# If either the HTTPS certificate or key do not exist, make some self-signed ones.
@ -103,42 +119,84 @@ class webserverInit():
# Index Handler
app.add_handlers(".*$", [
(r"/", tornado.web.RedirectHandler, {'url': '/home/'}),
(r"/", RedirectHandler, {'url': '/home/'}),
(r'/login', webserve.LoginHandler),
(r'%s(.*)(/?)' % options['web_root'], webserve.IndexHandler)
(r'%s(.*)(/?)' % self.options['web_root'], webserve.IndexHandler)
])
# Static Path Handler
app.add_handlers(".*$", [
('%s/%s/(.*)([^/]*)' % (options['web_root'], 'images'), MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'images'),
('%s/%s/(.*)([^/]*)' % (self.options['web_root'], 'images'), MultiStaticFileHandler,
{'paths': [os.path.join(self.options['data_root'], 'images'),
os.path.join(sickbeard.CACHE_DIR, 'images'),
os.path.join(sickbeard.CACHE_DIR, 'images', 'thumbnails')]}),
('%s/%s/(.*)([^/]*)' % (options['web_root'], 'css'), MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'css')]}),
('%s/%s/(.*)([^/]*)' % (options['web_root'], 'js'), MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'js')]})
('%s/%s/(.*)([^/]*)' % (self.options['web_root'], 'css'), MultiStaticFileHandler,
{'paths': [os.path.join(self.options['data_root'], 'css')]}),
('%s/%s/(.*)([^/]*)' % (self.options['web_root'], 'js'), MultiStaticFileHandler,
{'paths': [os.path.join(self.options['data_root'], 'js')]})
])
if enable_https:
protocol = "https"
server = tornado.httpserver.HTTPServer(app, no_keep_alive=True,
ssl_options={"certfile": https_cert, "keyfile": https_key})
self.server = HTTPServer(app, no_keep_alive=True,
ssl_options={"certfile": https_cert, "keyfile": https_key})
else:
protocol = "http"
server = tornado.httpserver.HTTPServer(app, no_keep_alive=True)
self.server = HTTPServer(app, no_keep_alive=True)
logger.log(u"Starting SickRage on " + protocol + "://" + str(options['host']) + ":" + str(
options['port']) + "/")
logger.log(u"Starting SickRage on " + protocol + "://" + str(self.options['host']) + ":" + str(
self.options['port']) + "/")
server.listen(options['port'], options['host'])
self.server.listen(self.options['port'], self.options['host'])
if self.thread == None or not self.thread.isAlive():
self.thread = threading.Thread(None, self.monitor, 'TORNADO')
def monitor(self):
while True:
currentTime = datetime.datetime.now()
if currentTime - self.lastRun > self.cycleTime:
self.lastRun = currentTime
try:
logger.log(u"Starting tornado", logger.DEBUG)
IOLoop.instance().start()
except Exception, e:
logger.log(u"Exception generated in tornado: " + ex(e), logger.ERROR)
logger.log(repr(traceback.format_exc()), logger.DEBUG)
if self.abort:
self.abort = False
self.thread = None
return
time.sleep(1)
def shutdown(self):
logger.logging.info('Shutting down tornado')
def run(self, force=False):
try:
self.amActive = True
tornado.ioloop.IOLoop.instance().start()
self.abort = True
self.server.stop()
deadline = time.time() + 10
io_loop = IOLoop.instance()
def stop_loop():
now = time.time()
if now < deadline:
if io_loop._callbacks:
io_loop.add_timeout(now + 1, stop_loop)
return
stop_loop()
self.thread.join(10)
except:
self.amActive = False
raise
pass
logger.logging.info('Tornado is now shutdown')