def handleException(self, object, request, exc_info, retry_allowed=1): if exc_info[0] is Conflict and retry_allowed: # This simulates a ZODB retry. raise Retry(exc_info) else: DefaultPublication.handleException(self, object, request, exc_info, retry_allowed)
def handleException(self, object, request, exc_info, retry_allowed=True): test.assertTrue(issubclass(exc_info[0], ErrorToRetry)) raise Retry()
def _handle_psycopg_exception(error): """Called from a exception handler for psycopg2.Error. If we have a serialization exception or a deadlock, we should retry the transaction by raising a Retry exception. Otherwise, we reraise. """ if isinstance(error, psycopg2.extensions.TransactionRollbackError): raise Retry(sys.exc_info()) raise
def testRetryNotAllowed(self): from ZODB.POSException import ConflictError from zope.publisher.interfaces import Retry try: raise ConflictError except: self.publication.handleException(self.object, self.request, sys.exc_info(), retry_allowed=False) value = ''.join(self.request.response._result).split() self.assertEqual(' '.join(value[:6]), 'Traceback (most recent call last): File') self.assertEqual( ' '.join(value[-8:]), 'in testRetryNotAllowed raise ConflictError' ' ConflictError: database conflict error') try: raise Retry(sys.exc_info()) except: self.publication.handleException(self.object, self.request, sys.exc_info(), retry_allowed=False) value = ''.join(self.request.response._result).split() self.assertEqual(' '.join(value[:6]), 'Traceback (most recent call last): File') self.assertEqual( ' '.join(value[-8:]), 'in testRetryNotAllowed raise Retry(sys.exc_info())' ' Retry: database conflict error') try: raise Retry except: self.publication.handleException(self.object, self.request, sys.exc_info(), retry_allowed=False) value = ''.join(self.request.response._result).split() self.assertEqual(' '.join(value[:6]), 'Traceback (most recent call last): File') self.assertEqual(' '.join(value[-6:]), 'in testRetryNotAllowed raise Retry' ' Retry: None')
def testRetryAllowed(self): from ZODB.POSException import ConflictError from zope.publisher.interfaces import Retry try: raise ConflictError except: self.assertRaises(Retry, self.publication.handleException, self.object, self.request, sys.exc_info(), retry_allowed=True) try: raise Retry(sys.exc_info()) except: self.assertRaises(Retry, self.publication.handleException, self.object, self.request, sys.exc_info(), retry_allowed=True)
def isConnected(self): """Check if we are connected to a database. Try to solve the dissapearing connection problem. For background, see http://mail.zope.org/pipermail/zope3-dev/2005-December/017052.html """ if self._v_connection is None: return False try: # Note, this might automatically re-connect to the DB # but then again might not... I've seen both. self._v_connection.ping() except MySQLdb.OperationalError: retry = Retry(sys.exc_info()) try: # this is a bare except because at this point # we are just trying to be nice closing the connection. self._v_connection.close() except: pass self._v_connection = None # raise a retry exception and let the publisher try again raise retry return True
def handleException(self, object, request, exc_info, retry_allowed=True): # Uninstall the database policy. store_selector = getUtility(IStoreSelector) if store_selector.get_current() is not None: db_policy = store_selector.pop() else: db_policy = None orig_env = request._orig_env ticks = tickcount.tickcount() if (hasattr(request, '_publicationticks_start') and ('launchpad.publicationticks' not in orig_env)): # The traversal process has been started but hasn't completed. assert 'launchpad.traversalticks' in orig_env, ( 'We reached the publication process so we must have finished ' 'the traversal.') ticks = tickcount.difference(request._publicationticks_start, ticks) request.setInWSGIEnvironment('launchpad.publicationticks', ticks) elif (hasattr(request, '_traversalticks_start') and ('launchpad.traversalticks' not in orig_env)): # The traversal process has been started but hasn't completed. ticks = tickcount.difference(request._traversalticks_start, ticks) request.setInWSGIEnvironment('launchpad.traversalticks', ticks) else: # The exception wasn't raised in the middle of the traversal nor # the publication, so there's nothing we need to do here. pass # Log an OOPS for DisconnectionErrors: we don't expect to see # disconnections as a routine event, so having information about them # is important. See Bug #373837 for more information. # We need to do this before we re-raise the exception as a Retry. if isinstance(exc_info[1], DisconnectionError): getUtility(IErrorReportingUtility).raising(exc_info, request) def should_retry(exc_info): if not retry_allowed: return False # If we get a LookupError and the default database being # used is a replica, raise a Retry exception instead of # returning the 404 error page. We do this in case the # LookupError is caused by replication lag. Our database # policy forces the use of the master database for retries. if (isinstance(exc_info[1], LookupError) and isinstance(db_policy, LaunchpadDatabasePolicy)): if db_policy.default_flavor == MASTER_FLAVOR: return False else: return True # Retry exceptions need to be propagated so they are # retried. Retry exceptions occur when an optimistic # transaction failed, such as we detected two transactions # attempting to modify the same resource. # DisconnectionError and TransactionRollbackError indicate # a database transaction failure, and should be retried # The appserver detects the error state, and a new database # connection is opened allowing the appserver to cope with # database or network outages. # An IntegrityError may be caused when we insert a row # into the database that already exists, such as two requests # doing an insert-or-update. It may succeed if we try again. if isinstance(exc_info[1], (Retry, DisconnectionError, IntegrityError, TransactionRollbackError)): return True return False # Re-raise Retry exceptions ourselves rather than invoke # our superclass handleException method, as it will log OOPS # reports etc. This would be incorrect, as transaction retry # is a normal part of operation. if should_retry(exc_info): if request.supportsRetry(): # Remove variables used for counting ticks as this request is # going to be retried. orig_env.pop('launchpad.traversalticks', None) orig_env.pop('launchpad.publicationticks', None) # Our endRequest needs to know if a retry is pending or not. request._wants_retry = True if isinstance(exc_info[1], Retry): raise raise Retry(exc_info) superclass = zope.app.publication.browser.BrowserPublication superclass.handleException(self, object, request, exc_info, retry_allowed) # If it's a HEAD request, we don't care about the body, regardless of # exception. # UPSTREAM: Should this be part of zope, # or is it only required because of our customisations? # - Andrew Bennetts, 2005-03-08 if request.method == 'HEAD': request.response.setResult('')
def handleException(self, object, request, exc_info, retry_allowed=True): # This transaction had an exception that reached the publisher. # It must definitely be aborted. transaction.abort() # Reraise Retry exceptions for the publisher to deal with. if retry_allowed and isinstance(exc_info[1], Retry): raise # Convert ConflictErrors to Retry exceptions. if retry_allowed and isinstance(exc_info[1], ConflictError): tryToLogWarning( 'ZopePublication', 'Competing writes/reads at %s: %s' % ( request.get('PATH_INFO', '???'), exc_info[1], ), ) raise Retry(exc_info) # Are there any reasons why we'd want to let application-level error # handling determine whether a retry is allowed or not? # Assume not for now. # Record the error with the ErrorReportingUtility self._logErrorWithErrorReportingUtility(object, request, exc_info) response = request.response response.reset() exception = None legacy_exception = not isinstance(exc_info[1], Exception) if legacy_exception: response.handleException(exc_info) if isinstance(exc_info[1], str): tryToLogWarning( 'Publisher received a legacy string exception: %s.' ' This will be handled by the request.' % exc_info[1]) else: tryToLogWarning( 'Publisher received a legacy classic class exception: %s.' ' This will be handled by the request.' % exc_info[1].__class__) else: # We definitely have an Exception # Set the request body, and abort the current transaction. self.beginErrorHandlingTransaction(request, object, 'application error-handling') view = None try: # We need to get a location, because some template content of # the exception view might require one. # # The object might not have a parent, because it might be a # method. If we don't have a `__parent__` attribute but have # an im_self or a __self__, use it. loc = object if not hasattr(object, '__parent__'): loc = removeSecurityProxy(object) # Try to get an object, since we apparently have a method # Note: We are guaranteed that an object has a location, # so just getting the instance the method belongs to is # sufficient. loc = getattr(loc, 'im_self', loc) loc = getattr(loc, '__self__', loc) # Protect the location with a security proxy loc = ProxyFactory(loc) # Give the exception instance its location and look up the # view. exception = LocationProxy(exc_info[1], loc, '') name = queryDefaultViewName(exception, request) if name is not None: view = zope.component.queryMultiAdapter( (exception, request), name=name) except: # Problem getting a view for this exception. Log an error. tryToLogException('Exception while getting view on exception') if view is not None: try: # We use mapply instead of self.callObject here # because we don't want to pass positional # arguments. The positional arguments were meant # for the published object, not an exception view. body = mapply(view, (), request) response.setResult(body) transaction.commit() if (ISystemErrorView.providedBy(view) and view.isSystemError()): # Got a system error, want to log the error # Lame hack to get around logging missfeature # that is fixed in Python 2.4 try: raise exc_info[0], exc_info[1], exc_info[2] except: logging.getLogger('SiteError').exception( str(request.URL), ) except: # Problem rendering the view for this exception. # Log an error. tryToLogException( 'Exception while rendering view on exception') # Record the error with the ErrorReportingUtility self._logErrorWithErrorReportingUtility( object, request, sys.exc_info()) view = None if view is None: # Either the view was not found, or view was set to None # because the view couldn't be rendered. In either case, # we let the request handle it. response.handleException(exc_info) transaction.abort() # See if there's an IExceptionSideEffects adapter for the # exception try: adapter = IExceptionSideEffects(exception, None) except: tryToLogException( 'Exception while getting IExceptionSideEffects adapter') adapter = None if adapter is not None: self.beginErrorHandlingTransaction( request, object, 'application error-handling side-effect') try: # Although request is passed in here, it should be # considered read-only. adapter(object, request, exc_info) transaction.commit() except: tryToLogException('Exception while calling' ' IExceptionSideEffects adapter') transaction.abort()