[IMP] http: LazyResponse rendering w/ concurrent access protection
QWeb's LazyResponse mechanism allows controllers to be inherited and to alter the rendering context (qwebcontext) *after* calling super(). However it caused the final rendering to occur outside the protection of the @service.model.check decorator that handles concurrent access errors. In rare cases the lazy rendering can be interrupted by a concurrent access error, for example when bootstrapping the the cache of asset bundles (as it INSERTs a bunch of attachments). By forcing the flatten()ing of the lazy response earlier, right after calling the route's endpoint(), it occurs within the protection of the @service.model.check, and benefits from the automatic retry. flatten() was modified to avoid rendering twice the template The flatten() in get_response() is still necessary for addons code that directly calls get_response(). Moved QWebException into openerp.exceptions next to the other exceptions classes - since it now has more global semantics. ~ The whole request dispatching code could really do with a complete redesign/simplification.
This commit is contained in:
@@ -27,6 +27,7 @@ import openerp.http
|
||||
import openerp.tools
|
||||
from openerp.tools.func import lazy_property
|
||||
import openerp.tools.lru
|
||||
from openerp.exceptions import QWebException
|
||||
from openerp.fields import Datetime
|
||||
from openerp.http import request
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
@@ -43,15 +44,6 @@ MAX_CSS_RULES = 4095
|
||||
#--------------------------------------------------------------------
|
||||
# QWeb template engine
|
||||
#--------------------------------------------------------------------
|
||||
class QWebException(Exception):
|
||||
def __init__(self, message, **kw):
|
||||
Exception.__init__(self, message)
|
||||
self.qweb = dict(kw)
|
||||
def pretty_xml(self):
|
||||
if 'node' not in self.qweb:
|
||||
return ''
|
||||
return etree.tostring(self.qweb['node'], pretty_print=True)
|
||||
|
||||
class QWebTemplateNotFound(QWebException):
|
||||
pass
|
||||
|
||||
|
||||
+12
-1
@@ -10,8 +10,9 @@ treated as a 'Server error'.
|
||||
If you consider introducing new exceptions, check out the test_exceptions addon.
|
||||
"""
|
||||
|
||||
from inspect import currentframe
|
||||
import logging
|
||||
from inspect import currentframe
|
||||
from lxml import etree
|
||||
from tools.func import frame_codeinfo
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -90,3 +91,13 @@ class DeferredException(Exception):
|
||||
def __init__(self, msg, tb):
|
||||
self.message = msg
|
||||
self.traceback = tb
|
||||
|
||||
class QWebException(Exception):
|
||||
def __init__(self, message, **kw):
|
||||
super(QWebException, self).__init__(message)
|
||||
self.qweb = dict(kw)
|
||||
|
||||
def pretty_xml(self):
|
||||
if 'node' not in self.qweb:
|
||||
return ''
|
||||
return etree.tostring(self.qweb['node'], pretty_print=True)
|
||||
|
||||
+8
-3
@@ -306,7 +306,11 @@ class WebRequest(object):
|
||||
# case, the request cursor is unusable. Rollback transaction to create a new one.
|
||||
if self._cr:
|
||||
self._cr.rollback()
|
||||
return self.endpoint(*a, **kw)
|
||||
result = self.endpoint(*a, **kw)
|
||||
if isinstance(result, Response) and result.is_qweb:
|
||||
# Early rendering of lazy responses to benefit from @service_model.check protection
|
||||
result.flatten()
|
||||
return result
|
||||
|
||||
if self.db:
|
||||
return checked_call(self.db, *args, **kwargs)
|
||||
@@ -1299,8 +1303,9 @@ class Response(werkzeug.wrappers.Response):
|
||||
""" Forces the rendering of the response's template, sets the result
|
||||
as response body and unsets :attr:`.template`
|
||||
"""
|
||||
self.response.append(self.render())
|
||||
self.template = None
|
||||
if self.template:
|
||||
self.response.append(self.render())
|
||||
self.template = None
|
||||
|
||||
class DisableCacheMiddleware(object):
|
||||
def __init__(self, app):
|
||||
|
||||
@@ -8,7 +8,7 @@ import threading
|
||||
import time
|
||||
|
||||
import openerp
|
||||
from openerp.exceptions import UserError, ValidationError
|
||||
from openerp.exceptions import UserError, ValidationError, QWebException
|
||||
from openerp.tools.translate import translate
|
||||
from openerp.tools.translate import _
|
||||
|
||||
@@ -111,7 +111,13 @@ def check(f):
|
||||
if openerp.registry(dbname)._init and not openerp.tools.config['test_enable']:
|
||||
raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
|
||||
return f(dbname, *args, **kwargs)
|
||||
except OperationalError, e:
|
||||
except (OperationalError, QWebException) as e:
|
||||
if isinstance(e, QWebException):
|
||||
cause = e.qweb.get('cause')
|
||||
if isinstance(cause, OperationalError):
|
||||
e = cause
|
||||
else:
|
||||
raise
|
||||
# Automatically retry the typical transaction serialization errors
|
||||
if e.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user