Skip to content

Commit

Permalink
Make sure conflicts are always retried and not masked by exception vi…
Browse files Browse the repository at this point in the history
…ews (#473)
  • Loading branch information
dataflake authored and Michael Howitz committed Feb 9, 2019
1 parent 7d3b842 commit e664a8c
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -14,6 +14,9 @@ https://github.com/zopefoundation/Zope/blob/4.0a6/CHANGES.rst
Fixes
+++++

- Make sure conflicts are always retried and not masked by exception views
(`#413 <https://github.com/zopefoundation/Zope/issues/413>`_)

- Fix faulty ZMI links due to missing URL-quoting
(`#391 <https://github.com/zopefoundation/Zope/issues/391>`_)

Expand Down
3 changes: 3 additions & 0 deletions src/ZPublisher/WSGIPublisher.py
Expand Up @@ -166,6 +166,9 @@ def transaction_pubevents(request, response, tm=transaction.manager):
notify(pubevents.PubFailure(
request, exc_info, request.supports_retry()))

if isinstance(exc, TransientError) and request.supports_retry():
reraise(*exc_info)

if not (exc_view_created or isinstance(exc, Unauthorized)):
reraise(*exc_info)
finally:
Expand Down
49 changes: 49 additions & 0 deletions src/ZPublisher/tests/test_WSGIPublisher.py
Expand Up @@ -469,6 +469,50 @@ def _publish(request, module_info):
finally:
HTTPRequest.retry_max_count = original_retry_max_count

def testCustomExceptionViewConflictErrorHandling(self):
# Make sure requests are retried as often as configured
# even if an exception view has been registered that
# matches ConflictError
from zope.interface import directlyProvides
from zope.publisher.browser import IDefaultBrowserLayer
registerExceptionView(Exception)
environ = self._makeEnviron()
start_response = DummyCallable()
_publish = DummyCallable()
_publish._raise = ConflictError('oops')
_request = DummyRequest()
directlyProvides(_request, IDefaultBrowserLayer)
_request.response = DummyResponse()
_request.retry_count = 0
_request.retry_max_count = 3
_request.environ = {}

def _close():
pass
_request.close = _close

def _retry():
_request.retry_count += 1
return _request
_request.retry = _retry

def _supports_retry():
return _request.retry_count < _request.retry_max_count
_request.supports_retry = _supports_retry

def _request_factory(stdin, environ, response):
return _request

# At first, retry_count is zero. Request has never been retried.
self.assertEqual(_request.retry_count, 0)
app_iter = self._callFUT(environ, start_response, _publish,
_request_factory=_request_factory)

# In the end the error view is rendered, but the request should
# have been retried up to retry_max_count times
self.assertTrue(app_iter[1].startswith('Exception View: ConflictError'))
self.assertEqual(_request.retry_count, _request.retry_max_count)

def testCustomExceptionViewUnauthorized(self):
from AccessControl import Unauthorized
registerExceptionView(IUnauthorized)
Expand Down Expand Up @@ -717,6 +761,11 @@ def setBody(self, body):

body = property(lambda self: self._body, setBody)

def setStatus(self, status, reason=None, lock=None):
self._status = status

status = property(lambda self: self._status, setStatus)


class DummyCallable(object):
_called_with = _raise = _result = None
Expand Down

0 comments on commit e664a8c

Please sign in to comment.