From 46bd600da01ee33e8035682d83875752e08d39b0 Mon Sep 17 00:00:00 2001 From: echel0n Date: Mon, 8 Dec 2014 07:34:14 -0800 Subject: [PATCH] Improved async threading code for WebUI --- sickbeard/webserve.py | 96 ++++++++++++++++++++++++--------------- sickbeard/webserveInit.py | 8 ++-- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index e921a965..9abbae24 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -17,8 +17,8 @@ # along with SickRage. If not, see . from __future__ import with_statement -import threading +import threading import traceback import os import time @@ -75,11 +75,31 @@ except ImportError: from Cheetah.Template import Template +from functools import wraps from tornado.routes import route from tornado.web import RequestHandler, authenticated, asynchronous from bug_tracker import BugTracker +route_locks = {} + +def run_async(func): + @wraps(func) + def async_func(*args, **kwargs): + func_hl = threading.Thread(target = func, args = args, kwargs = kwargs) + func_hl.start() + + return async_func + +@run_async +def run_handler(route, kwargs, callback = None): + try: + res = route(**kwargs) + callback(res, route) + except: + logger.log('Failed doing api request "%s": %s' % (route, traceback.format_exc()), logger.ERROR) + callback({'success': False, 'error': 'Failed returning results'}, route) + def page_not_found(rh): index_url = sickbeard.WEB_ROOT url = rh.request.uri[len(index_url):] @@ -91,23 +111,6 @@ def page_not_found(rh): rh.set_status(404) rh.write('Wrong API key used') -class Worker(threading.Thread): - def __init__(self, func, params=None, callback=None, *args, **kwargs): - super(Worker, self).__init__(*args, **kwargs) - self.callback = callback - self.func = func - self.params = params - - def run(self): - # Get response - resp = self.func(**self.params) - if resp: - # Issue callback - try: - self.callback(ek.ss(resp).encode('utf-8', 'xmlcharrefreplace')) - except: - self.callback(resp) - class PageTemplate(Template): def __init__(self, rh, *args, **kwargs): kwargs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/", kwargs['file']) @@ -260,31 +263,52 @@ class WebHandler(BaseHandler): route = route.strip('/') try: - route = getattr(self, route, self.index) - if not callable(route): - self.set_status(404) - self.write('Could not load webui module') - return + route = getattr(self, route) except: - page_not_found(self) - return + route = getattr(self, 'index') - # Sanitize argument lists: - params = self.request.arguments - for arg, value in params.items(): - if len(value) == 1: - params[arg] = value[0] + # acquire route lock + route_locks[route] = threading.Lock() + route_locks[route].acquire() - Worker(route, params, self.worker_done).start() - - def worker_done(self, value): try: - self.write(value) + + # Sanitize argument lists: + kwargs = self.request.arguments + for arg, value in kwargs.items(): + if len(value) == 1: + kwargs[arg] = value[0] + + run_handler(route, kwargs, callback=self.taskFinished) + except: + route_locks[route].release() + page_not_found(self) + + def taskFinished(self, result, route): + try: + if result: + # encode result data + result = ek.ss(result).encode('utf-8', 'xmlcharrefreplace') + + # Check JSONP callback + jsonp_callback = self.get_argument('callback_func', default=None) + + if jsonp_callback: + self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')') + self.set_header("Content-Type", "text/javascript") + self.finish() + else: + self.write(result) + self.finish() + except UnicodeDecodeError: + logger.log('Failed proper encode: %s' % traceback.format_exc(), logger.ERROR) except: logger.log("Failed doing web request '%s': %s" % (route, traceback.format_exc()), logger.ERROR) - self.write({'success': False, 'error': 'Failed returning results'}) + try:self.finish({'success': False, 'error': 'Failed returning results'}) + except:pass - self.finish() + # release route lock + route_locks[route].release() # link post to get post = get diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index 2e8d75f1..8d68084d 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -12,6 +12,7 @@ from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.routes import route + class MultiStaticFileHandler(StaticFileHandler): def initialize(self, paths, default_filename=None): self.paths = paths @@ -76,7 +77,7 @@ class SRWebServer(threading.Thread): if self.enable_https: # If either the HTTPS certificate or key do not exist, make some self-signed ones. if not (self.https_cert and os.path.exists(self.https_cert)) or not ( - self.https_key and os.path.exists(self.https_key)): + self.https_key and os.path.exists(self.https_key)): if not create_https_certificates(self.https_cert, self.https_key): logger.log(u"Unable to create CERT/KEY files, disabling HTTPS") sickbeard.ENABLE_HTTPS = False @@ -101,9 +102,10 @@ class SRWebServer(threading.Thread): self.app.add_handlers(".*$", [ (r'%s(/?)' % self.options['api_root'], ApiHandler), (r'%s/getkey(/?)' % self.options['web_root'], KeyHandler), - (r'%s/api/builder' % self.options['web_root'], RedirectHandler, {"url": self.options['web_root'] + '/apibuilder/'}), + (r'%s/api/builder' % self.options['web_root'], RedirectHandler, + {"url": self.options['web_root'] + '/apibuilder/'}), (r'%s/login(/?)' % self.options['web_root'], LoginHandler), - (r'%s/logout(/?)' % self.options['web_root'], LogoutHandler) + (r'%s/logout(/?)' % self.options['web_root'], LogoutHandler), ] + route.get_routes()) # Static Path Handlers