diff --git a/oml/item/icons.py b/oml/item/icons.py index a42a8a4..87a6a5b 100644 --- a/oml/item/icons.py +++ b/oml/item/icons.py @@ -8,8 +8,10 @@ import tornado.concurrent import tornado.gen import tornado.ioloop import tornado.web +from concurrent.futures import ThreadPoolExecutor +from tornado.concurrent import run_on_executor + -from oxtornado import run_async from settings import icons_db_path, static_path from utils import resize_image, is_svg import db @@ -18,6 +20,9 @@ import db import logging logger = logging.getLogger(__name__) +MAX_WORKERS = 4 + + class Icons(dict): def __init__(self, db): @@ -174,53 +179,20 @@ def get_icon_sync(id, type_, size): data = '' return data -@run_async -def get_icon(id, type_, size, callback): - callback(get_icon_sync(id, type_, size)) - def clear_default_cover_cache(): icons.clear('default:cover:') -@run_async -def get_icon_app(id, type_, size, callback): - with db.session(): - from item.models import Item - item = Item.get(id) - if not item: - data = '' - else: - if type_ == 'cover' and not item.meta.get('cover'): - type_ = 'preview' - if type_ == 'preview' and not item.files.count(): - type_ = 'cover' - if size: - skey = '%s:%s:%s' % (type_, id, size) - key = '%s:%s' % (type_, id) - data = None - if size: - data = icons[skey] - if data: - size = None - if not data: - data = icons[key] - if not data: - data = icons.default_cover() - size = None - if size: - try: - data = resize_image(data, size=size) - icons[skey] = data - except: - pass - data = bytes(data) or '' - callback(data) class IconHandler(tornado.web.RequestHandler): + executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) def initialize(self): pass - @tornado.web.asynchronous + @run_on_executor + def get_icon(self, id, type_, size): + return get_icon_sync(id, type_, size) + @tornado.gen.coroutine def get(self, id, type_, size=None): @@ -232,7 +204,7 @@ class IconHandler(tornado.web.RequestHandler): self.set_header('Content-Type', 'image/jpeg') - response = yield tornado.gen.Task(get_icon, id, type_, size) + response = yield self.get_icon(id, type_, size) if not response: self.set_status(404) return diff --git a/oml/oxtornado.py b/oml/oxtornado.py index 322c6a8..2ed78ce 100644 --- a/oml/oxtornado.py +++ b/oml/oxtornado.py @@ -12,13 +12,15 @@ import tornado.ioloop import tornado.web import tornado.gen import tornado.concurrent -from threading import Thread -from functools import wraps +from concurrent.futures import ThreadPoolExecutor +from tornado.concurrent import run_on_executor + import logging logger = logging.getLogger(__name__) +MAX_WORKERS = 4 def json_response(data=None, status=200, text='ok'): if not data: @@ -37,15 +39,6 @@ def json_dumps(obj): indent = 2 return json.dumps(obj, indent=indent, default=_to_json, ensure_ascii=False).encode() -def run_async(func): - @wraps(func) - def async_func(*args, **kwargs): - func_hl = Thread(target=func, args=args, kwargs=kwargs) - func_hl.start() - return func_hl - - return async_func - def trim(docstring): if not docstring: return '' @@ -75,44 +68,46 @@ def trim(docstring): def defaultcontext(): yield -@run_async -def api_task(context, request, callback): - import settings - if context == None: - context = defaultcontext - action = request.arguments.get('action', [None])[0].decode('utf-8') - data = request.arguments.get('data', [b'{}'])[0] - data = json.loads(data.decode('utf-8')) if data else {} - if not action: - methods = list(actions.keys()) - api = [] - for f in sorted(methods): - api.append({'name': f, - 'doc': actions.doc(f).replace('\n', '
\n')}) - response = json_response(api) - else: - if settings.DEBUG_API: - logger.debug('API %s %s', action, data) - f = actions.get(action) - if f: - with context(): - try: - response = f(data) - except: - logger.debug('FAILED %s %s', action, data, exc_info=True) - response = json_response(status=500, text='%s failed' % action) - else: - response = json_response(status=400, text='Unknown action %s' % action) - callback(response) - class ApiHandler(tornado.web.RequestHandler): + executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) + def initialize(self, context=None): self._context = context def get(self): self.write('use POST') - @tornado.web.asynchronous + @run_on_executor + def api_task(self, request): + import settings + context = self._context + if context == None: + context = defaultcontext + action = request.arguments.get('action', [None])[0].decode('utf-8') + data = request.arguments.get('data', [b'{}'])[0] + data = json.loads(data.decode('utf-8')) if data else {} + if not action: + methods = list(actions.keys()) + api = [] + for f in sorted(methods): + api.append({'name': f, + 'doc': actions.doc(f).replace('\n', '
\n')}) + response = json_response(api) + else: + if settings.DEBUG_API: + logger.debug('API %s %s', action, data) + f = actions.get(action) + if f: + with context(): + try: + response = f(data) + except: + logger.debug('FAILED %s %s', action, data, exc_info=True) + response = json_response(status=500, text='%s failed' % action) + else: + response = json_response(status=400, text='Unknown action %s' % action) + return response + @tornado.gen.coroutine def post(self): if 'origin' in self.request.headers and self.request.host not in self.request.headers['origin']: @@ -121,7 +116,7 @@ class ApiHandler(tornado.web.RequestHandler): self.write('') return - response = yield tornado.gen.Task(api_task, self._context, self.request) + response = yield self.api_task(self.request) if not 'status' in response: response = json_response(response) response = json_dumps(response)