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 callObject(self, request, ob): """See `zope.publisher.interfaces.IPublication`. Our implementation make sure that no result is returned on redirect. It also sets the launchpad.userid and launchpad.pageid WSGI environment variables. """ request._publicationticks_start = tickcount.tickcount() if request.response.getStatus() in [301, 302, 303, 307]: return '' request.setInWSGIEnvironment( 'launchpad.userid', request.principal.id) # The view may be security proxied view = removeSecurityProxy(ob) # It's possible that the view is a bound method. view = getattr(view, 'im_self', view) context = removeSecurityProxy(getattr(view, 'context', None)) pageid = self.constructPageID(view, context) request.setInWSGIEnvironment('launchpad.pageid', pageid) # And spit the pageid out to our tracelog. tracelog(request, 'p', pageid) # For status URLs, where we really don't want to have any DB access # at all, ensure that all flag lookups will stop early. if pageid in ( 'RootObject:OpStats', 'RootObject:+opstats', 'RootObject:+haproxy'): request.features = NullFeatureController() features.install_feature_controller(request.features) # Calculate the hard timeout: needed because featureflags can be used # to control the hard timeout, and they trigger DB access, but our # DB tracers are not safe for reentrant use, so we must do this # outside of the SQL stack. We must also do it after traversal so that # the view is known and can be used in scope resolution. As we # actually stash the pageid after afterTraversal, we need to do this # even later. da.set_permit_timeout_from_features(True) da._get_request_timeout() if isinstance(removeSecurityProxy(ob), METHOD_WRAPPER_TYPE): # this is a direct call on a C-defined method such as __repr__ or # dict.__setitem__. Apparently publishing this is possible and # acceptable, at least in the case of # lp.services.webapp.servers.PrivateXMLRPCPublication. # mapply cannot handle these methods because it cannot introspect # them. We'll just call them directly. return ob(*request.getPositionalArguments()) return mapply(ob, request.getPositionalArguments(), request)
def callObject(self, request, ob): """See `zope.publisher.interfaces.IPublication`. Our implementation make sure that no result is returned on redirect. It also sets the launchpad.userid and launchpad.pageid WSGI environment variables. """ request._publicationticks_start = tickcount.tickcount() if request.response.getStatus() in [301, 302, 303, 307]: return '' request.setInWSGIEnvironment('launchpad.userid', request.principal.id) # The view may be security proxied view = removeSecurityProxy(ob) # It's possible that the view is a bound method. view = getattr(view, 'im_self', view) context = removeSecurityProxy(getattr(view, 'context', None)) pageid = self.constructPageID(view, context) request.setInWSGIEnvironment('launchpad.pageid', pageid) # And spit the pageid out to our tracelog. tracelog(request, 'p', pageid) # For status URLs, where we really don't want to have any DB access # at all, ensure that all flag lookups will stop early. if pageid in ('RootObject:OpStats', 'RootObject:+opstats', 'RootObject:+haproxy'): request.features = NullFeatureController() features.install_feature_controller(request.features) # Calculate the hard timeout: needed because featureflags can be used # to control the hard timeout, and they trigger DB access, but our # DB tracers are not safe for reentrant use, so we must do this # outside of the SQL stack. We must also do it after traversal so that # the view is known and can be used in scope resolution. As we # actually stash the pageid after afterTraversal, we need to do this # even later. da.set_permit_timeout_from_features(True) da._get_request_timeout() if isinstance(removeSecurityProxy(ob), METHOD_WRAPPER_TYPE): # this is a direct call on a C-defined method such as __repr__ or # dict.__setitem__. Apparently publishing this is possible and # acceptable, at least in the case of # lp.services.webapp.servers.PrivateXMLRPCPublication. # mapply cannot handle these methods because it cannot introspect # them. We'll just call them directly. return ob(*request.getPositionalArguments()) return mapply(ob, request.getPositionalArguments(), request)
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 beforeTraversal(self, request): notify(StartRequestEvent(request)) request._traversalticks_start = tickcount.tickcount() threadid = thread.get_ident() threadrequestfile = open_for_writing( 'logs/thread-%s.request' % threadid, 'w') try: request_txt = unicode(request).encode('UTF-8') except Exception: request_txt = 'Exception converting request to string\n\n' try: request_txt += traceback.format_exc() except: request_txt += 'Unable to render traceback!' threadrequestfile.write(request_txt) threadrequestfile.close() # Tell our custom database adapter that the request has started. da.set_request_started() newInteraction(request) transaction.begin() # Now we are logged in, install the correct IDatabasePolicy for # this request. db_policy = IDatabasePolicy(request) getUtility(IStoreSelector).push(db_policy) getUtility(IOpenLaunchBag).clear() # Set the default layer. adapters = getGlobalSiteManager().adapters layer = adapters.lookup((providedBy(request), ), IDefaultSkin, '') if layer is not None: layers.setAdditionalLayer(request, layer) principal = self.getPrincipal(request) request.setPrincipal(principal) self.maybeRestrictToTeam(request) maybe_block_offsite_form_post(request)
def beforeTraversal(self, request): notify(StartRequestEvent(request)) request._traversalticks_start = tickcount.tickcount() threadid = thread.get_ident() threadrequestfile = open_for_writing( 'logs/thread-%s.request' % threadid, 'w') try: request_txt = unicode(request).encode('UTF-8') except Exception: request_txt = 'Exception converting request to string\n\n' try: request_txt += traceback.format_exc() except: request_txt += 'Unable to render traceback!' threadrequestfile.write(request_txt) threadrequestfile.close() # Tell our custom database adapter that the request has started. da.set_request_started() newInteraction(request) transaction.begin() # Now we are logged in, install the correct IDatabasePolicy for # this request. db_policy = IDatabasePolicy(request) getUtility(IStoreSelector).push(db_policy) getUtility(IOpenLaunchBag).clear() # Set the default layer. adapters = getGlobalSiteManager().adapters layer = adapters.lookup((providedBy(request),), IDefaultSkin, '') if layer is not None: layers.setAdditionalLayer(request, layer) principal = self.getPrincipal(request) request.setPrincipal(principal) self.maybeRestrictToTeam(request) maybe_block_offsite_form_post(request)
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('')