一次tornado升级导致的bug,排查了好久,比较坑
之前一直稳定运行的代码,我引进新项目的时候,忽然报错
'Application' object has no attribute 'handlers'"
困扰许久。而且是时而正常,时而出错。
。
无法忍受,后开启debug模式排查,发现原来是tornado4.5起升级了,引入了新的tornado.routing模块。
4.4的Application代码如下:
class Application(httputil.HTTPServerConnectionDelegate):
"""A collection of request handlers that make up a web application.
``static_handler_class`` setting.
"""
def __init__(self, handlers=None, default_host="", transforms=None,
**settings):
if transforms is None:
self.transforms = []
if settings.get("compress_response") or settings.get("gzip"):
self.transforms.append(GZipContentEncoding)
else:
self.transforms = transforms
self.handlers = []
self.named_handlers = {}
self.default_host = default_host
self.settings = settings
self.ui_modules = {'linkify': _linkify,
'xsrf_form_html': _xsrf_form_html,
'Template': TemplateModule,
}
self.ui_methods = {}
self._load_ui_modules(settings.get("ui_modules", {}))
self._load_ui_methods(settings.get("ui_methods", {}))
if self.settings.get("static_path"):
path = self.settings["static_path"]
handlers = list(handlers or [])
static_url_prefix = settings.get("static_url_prefix",
"/static/")
static_handler_class = settings.get("static_handler_class",
StaticFileHandler)
static_handler_args = settings.get("static_handler_args", {})
static_handler_args['path'] = path
for pattern in [re.escape(static_url_prefix) + r"(.*)",
r"/(favicon\.ico)", r"/(robots\.txt)"]:
handlers.insert(0, (pattern, static_handler_class,
static_handler_args))
if handlers:
self.add_handlers(".*$", handlers)
if self.settings.get('debug'):
self.settings.setdefault('autoreload', True)
self.settings.setdefault('compiled_template_cache', False)
self.settings.setdefault('static_hash_cache', False)
self.settings.setdefault('serve_traceback', True)
# Automatically reload modified modules
if self.settings.get('autoreload'):
from tornado import autoreload
autoreload.start()
到4.5代码已经变成:
class Application(ReversibleRouter):
"""A collection of request handlers that make up a web application.
.. versionchanged:: 4.5
Integration with the new `tornado.routing` module.
"""
def __init__(self, handlers=None, default_host=None, transforms=None,
**settings):
if transforms is None:
self.transforms = []
if settings.get("compress_response") or settings.get("gzip"):
self.transforms.append(GZipContentEncoding)
else:
self.transforms = transforms
self.default_host = default_host
self.settings = settings
self.ui_modules = {'linkify': _linkify,
'xsrf_form_html': _xsrf_form_html,
'Template': TemplateModule,
}
self.ui_methods = {}
self._load_ui_modules(settings.get("ui_modules", {}))
self._load_ui_methods(settings.get("ui_methods", {}))
if self.settings.get("static_path"):
path = self.settings["static_path"]
handlers = list(handlers or [])
static_url_prefix = settings.get("static_url_prefix",
"/static/")
static_handler_class = settings.get("static_handler_class",
StaticFileHandler)
static_handler_args = settings.get("static_handler_args", {})
static_handler_args['path'] = path
for pattern in [re.escape(static_url_prefix) + r"(.*)",
r"/(favicon\.ico)", r"/(robots\.txt)"]:
handlers.insert(0, (pattern, static_handler_class,
static_handler_args))
if self.settings.get('debug'):
self.settings.setdefault('autoreload', True)
self.settings.setdefault('compiled_template_cache', False)
self.settings.setdefault('static_hash_cache', False)
self.settings.setdefault('serve_traceback', True)
self.wildcard_router = _ApplicationRouter(self, handlers)
self.default_router = _ApplicationRouter(self, [
Rule(AnyMatches(), self.wildcard_router)
])
# Automatically reload modified modules
if self.settings.get('autoreload'):
from tornado import autoreload
autoreload.start()
很容易的看到,Application继承的父类都变了。
新版本的父类ReversibleRouter正是新的tornado.routing模块里面定义的。
我们的项目代码如下:
class Application(object):
def __init__(self):
from tornado.options import options
self._options = options
self._settings = settings
self._beforecallback = None
self._shutdown_callback = []
self._app = None
def call_shutdown_callback(self):
for callback in self._shutdown_callback:
callback()
def init_settings(self):
from config import FILE_UPLOAD_PATH
import tornado.options
tornado.options.parse_command_line()
self._settings['static_path'] = FILE_UPLOAD_PATH
self._settings['static_url_prefix'] = '/upload/'
self._settings["debug"] = self._options.debug
self._settings['module'] = self._options.module
if not self._settings['module']:
print("the module parameter is required.")
exit(0)
else:
context['module'] = self._settings['module']
if self._settings["debug"]:
self._settings["autoreload"] = True
self.install_autoreload_hook()
if not self._options.debug:
args = sys.argv
args.append("--log_file_prefix=%s" % settings['logfile'])
tornado.options.parse_command_line(args)
@property
def options(self):
return self._options
@property
def handlers(self):
from urls import handlers
return handlers
@property
def settings(self):
return self._settings
def get_app(self):
self._beforecallback(self)
self.init_settings()
self.install_event_hooks()
self.install_middleware()
self.install_rbac()
self.install_message_backend()
from tornado.web import Application
return Application(self.handlers, **self._settings)
本来计划对着新的代码修改下我们的代码, 适配新的版本,不过发现变化比较大。比如这个Route里面的URLSpec也从tornado.web移动到tornado.routing里面了,代码整体变动太大,所以还是去修改服务器的tornado版本吧!
class Route(object):
urls = []
def __call__(self, url, name=None):
def _(cls):
if url.startswith("/"):
_url = r"%s" % url
else:
_url = r"/api/%s/%s" % (API_VERSION, url)
self.urls.append(tornado.web.URLSpec(_url, cls, name=name))
return cls
return _
结论:
虽然问题不是代码层面解决的,但是也是感慨万千。还有就是感觉运气有点爆棚,上周在服务器一pip install tornado装的4.4, 隔了一周在服务器二pip install tornado就装到4.5了,而且关键变化还挺大,真是坑啊!