Example #1
0
 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()
Example #3
0
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)
Example #6
0
    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('')
Example #8
0
    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()