def afterCall(self, request, ob): """See `zope.publisher.interfaces.IPublication`. Our implementation calls self.finishReadOnlyRequest(), which by default aborts the transaction, for read-only requests. Because of this we cannot chain to the superclass and implement the whole behaviour here. """ assert hasattr(request, '_publicationticks_start'), ( 'request._publicationticks_start, which should have been set by ' 'callObject(), was not found.') ticks = tickcount.difference(request._publicationticks_start, tickcount.tickcount()) request.setInWSGIEnvironment('launchpad.publicationticks', ticks) # Calculate SQL statement statistics. sql_statements = da.get_request_statements() sql_milliseconds = sum( endtime - starttime for starttime, endtime, id, statement, tb in sql_statements) # Log publication tickcount, sql statement count, and sql time # to the tracelog. tracelog(request, 't', '%d %d %d' % (ticks, len(sql_statements), sql_milliseconds)) # Annotate the transaction with user data. That was done by # zope.app.publication.zopepublication.ZopePublication. txn = transaction.get() self.annotateTransaction(txn, request, ob) # Abort the transaction on a read-only request. # NOTHING AFTER THIS SHOULD CAUSE A RETRY. if request.method in ['GET', 'HEAD']: self.finishReadOnlyRequest(request, ob, txn) elif txn.isDoomed(): # The following sends an abort to the database, even though the # transaction is still doomed. txn.abort() else: txn.commit() # Don't render any content for a HEAD. This was done # by zope.app.publication.browser.BrowserPublication if request.method == 'HEAD': request.response.setResult('') try: getUtility(IStoreSelector).pop() except IndexError: # We have to cope with no database policy being installed # to allow doc/webapp-publication.txt tests to pass. These # tests rely on calling the afterCall hook without first # calling beforeTraversal or doing proper cleanup. pass
def afterCall(self, request, ob): """See `zope.publisher.interfaces.IPublication`. Our implementation calls self.finishReadOnlyRequest(), which by default aborts the transaction, for read-only requests. Because of this we cannot chain to the superclass and implement the whole behaviour here. """ assert hasattr(request, '_publicationticks_start'), ( 'request._publicationticks_start, which should have been set by ' 'callObject(), was not found.') ticks = tickcount.difference( request._publicationticks_start, tickcount.tickcount()) request.setInWSGIEnvironment('launchpad.publicationticks', ticks) # Calculate SQL statement statistics. sql_statements = da.get_request_statements() sql_milliseconds = sum( endtime - starttime for starttime, endtime, id, statement, tb in sql_statements) # Log publication tickcount, sql statement count, and sql time # to the tracelog. tracelog(request, 't', '%d %d %d' % ( ticks, len(sql_statements), sql_milliseconds)) # Annotate the transaction with user data. That was done by # zope.app.publication.zopepublication.ZopePublication. txn = transaction.get() self.annotateTransaction(txn, request, ob) # Abort the transaction on a read-only request. # NOTHING AFTER THIS SHOULD CAUSE A RETRY. if request.method in ['GET', 'HEAD']: self.finishReadOnlyRequest(request, ob, txn) elif txn.isDoomed(): # The following sends an abort to the database, even though the # transaction is still doomed. txn.abort() else: txn.commit() # Don't render any content for a HEAD. This was done # by zope.app.publication.browser.BrowserPublication if request.method == 'HEAD': request.response.setResult('') try: getUtility(IStoreSelector).pop() except IndexError: # We have to cope with no database policy being installed # to allow doc/webapp-publication.txt tests to pass. These # tests rely on calling the afterCall hook without first # calling beforeTraversal or doing proper cleanup. pass
def afterTraversal(self, request, ob): """See zope.publisher.interfaces.IPublication. This hook does not invoke our parent's afterTraversal hook in zopepublication.py because we don't want to call _maybePlacefullyAuthenticate. """ # Log the URL including vhost information to the ZServer tracelog. tracelog(request, 'u', request.getURL()) assert hasattr(request, '_traversalticks_start'), ( 'request._traversalticks_start, which should have been set by ' 'beforeTraversal(), was not found.') ticks = tickcount.difference(request._traversalticks_start, tickcount.tickcount()) request.setInWSGIEnvironment('launchpad.traversalticks', ticks)
def afterTraversal(self, request, ob): """See zope.publisher.interfaces.IPublication. This hook does not invoke our parent's afterTraversal hook in zopepublication.py because we don't want to call _maybePlacefullyAuthenticate. """ # Log the URL including vhost information to the ZServer tracelog. tracelog(request, 'u', request.getURL()) assert hasattr(request, '_traversalticks_start'), ( 'request._traversalticks_start, which should have been set by ' 'beforeTraversal(), was not found.') ticks = tickcount.difference( request._traversalticks_start, tickcount.tickcount()) request.setInWSGIEnvironment('launchpad.traversalticks', ticks)
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): # 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('')