Navigation

Source code for tornado.stack_context

#!/usr/bin/env python## Copyright 2010 Facebook## Licensed under the Apache License, Version 2.0 (the "License"); you may# not use this file except in compliance with the License. You may obtain# a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the# License for the specific language governing permissions and limitations# under the License.'''StackContext allows applications to maintain threadlocal-like statethat follows execution as it moves to other execution contexts.The motivating examples are to eliminate the need for explicitasync_callback wrappers (as in tornado.web.RequestHandler), and toallow some additional context to be kept for logging.This is slightly magic, but it's an extension of the idea that an exceptionhandler is a kind of stack-local state and when that stack is suspendedand resumed in a new context that state needs to be preserved. StackContextshifts the burden of restoring that state from each call site (e.g.wrapping each AsyncHTTPClient callback in async_callback) to the mechanismsthat transfer control from one context to another (e.g. AsyncHTTPClientitself, IOLoop, thread pools, etc).Example usage:: @contextlib.contextmanager def die_on_error(): try: yield except Exception: logging.error("exception in asynchronous operation",exc_info=True) sys.exit(1) with StackContext(die_on_error): # Any exception thrown here *or in callback and its desendents* # will cause the process to exit instead of spinning endlessly # in the ioloop. http_client.fetch(url, callback) ioloop.start()Most applications shouln't have to work with `StackContext` directly.Here are a few rules of thumb for when it's necessary:* If you're writing an asynchronous library that doesn't rely on a stack_context-aware library like `tornado.ioloop` or `tornado.iostream` (for example, if you're writing a thread pool), use `stack_context.wrap()` before any asynchronous operations to capture the stack context from where the operation was started.* If you're writing an asynchronous library that has some shared resources (such as a connection pool), create those shared resources within a ``with stack_context.NullContext():`` block. This will prevent ``StackContexts`` from leaking from one request to another.* If you want to write something like an exception handler that will persist across asynchronous calls, create a new `StackContext` (or `ExceptionStackContext`), and make your asynchronous calls in a ``with`` block that references your `StackContext`.'''from__future__importabsolute_import,division,with_statementimportcontextlibimportfunctoolsimportitertoolsimportoperatorimportsysimportthreadingfromtornado.utilimportraise_exc_infoclass_State(threading.local):def__init__(self):self.contexts=()_state=_State()

[docs]classStackContext(object):'''Establishes the given context as a StackContext that will be transferred. Note that the parameter is a callable that returns a context manager, not the context itself. That is, where for a non-transferable context manager you would say:: with my_context(): StackContext takes the function itself rather than its result:: with StackContext(my_context): The result of ``with StackContext() as cb:`` is a deactivation callback. Run this callback when the StackContext is no longer needed to ensure that it is not propagated any further (note that deactivating a context does not affect any instances of that context that are currently pending). This is an advanced feature and not necessary in most applications. '''def__init__(self,context_factory,_active_cell=None):self.context_factory=context_factoryself.active_cell=_active_cellor[True]# Note that some of this code is duplicated in ExceptionStackContext# below. ExceptionStackContext is more common and doesn't need# the full generality of this class.def__enter__(self):self.old_contexts=_state.contexts# _state.contexts is a tuple of (class, arg, active_cell) tuples_state.contexts=(self.old_contexts+((StackContext,self.context_factory,self.active_cell),))try:self.context=self.context_factory()self.context.__enter__()exceptException:_state.contexts=self.old_contextsraisereturnlambda:operator.setitem(self.active_cell,0,False)def__exit__(self,type,value,traceback):try:returnself.context.__exit__(type,value,traceback)finally:_state.contexts=self.old_contexts

[docs]classExceptionStackContext(object):'''Specialization of StackContext for exception handling. The supplied exception_handler function will be called in the event of an uncaught exception in this context. The semantics are similar to a try/finally clause, and intended use cases are to log an error, close a socket, or similar cleanup actions. The exc_info triple (type, value, traceback) will be passed to the exception_handler function. If the exception handler returns true, the exception will be consumed and will not be propagated to other exception handlers. '''def__init__(self,exception_handler,_active_cell=None):self.exception_handler=exception_handlerself.active_cell=_active_cellor[True]def__enter__(self):self.old_contexts=_state.contexts_state.contexts=(self.old_contexts+((ExceptionStackContext,self.exception_handler,self.active_cell),))returnlambda:operator.setitem(self.active_cell,0,False)def__exit__(self,type,value,traceback):try:iftypeisnotNone:returnself.exception_handler(type,value,traceback)finally:_state.contexts=self.old_contexts

[docs]classNullContext(object):'''Resets the StackContext. Useful when creating a shared resource on demand (e.g. an AsyncHTTPClient) where the stack that caused the creating is not relevant to future operations. '''def__enter__(self):self.old_contexts=_state.contexts_state.contexts=()def__exit__(self,type,value,traceback):_state.contexts=self.old_contexts

class_StackContextWrapper(functools.partial):pass

[docs]defwrap(fn):'''Returns a callable object that will restore the current StackContext when executed. Use this whenever saving a callback to be executed later in a different execution context (either in a different thread or asynchronously in the same thread). '''iffnisNoneorfn.__class__is_StackContextWrapper:returnfn# functools.wraps doesn't appear to work on functools.partial objects#@functools.wraps(fn)defwrapped(callback,contexts,*args,**kwargs):ifcontextsis_state.contextsornotcontexts:callback(*args,**kwargs)returnifnot_state.contexts:new_contexts=[cls(arg,active_cell)for(cls,arg,active_cell)incontextsifactive_cell[0]]# If we're moving down the stack, _state.contexts is a prefix# of contexts. For each element of contexts not in that prefix,# create a new StackContext object.# If we're moving up the stack (or to an entirely different stack),# _state.contexts will have elements not in contexts. Use# NullContext to clear the state and then recreate from contexts.elif(len(_state.contexts)>len(contexts)orany(a[1]isnotb[1]fora,binitertools.izip(_state.contexts,contexts))):# contexts have been removed or changed, so start overnew_contexts=([NullContext()]+[cls(arg,active_cell)for(cls,arg,active_cell)incontextsifactive_cell[0]])else:new_contexts=[cls(arg,active_cell)for(cls,arg,active_cell)incontexts[len(_state.contexts):]ifactive_cell[0]]iflen(new_contexts)>1:with_nested(*new_contexts):callback(*args,**kwargs)elifnew_contexts:withnew_contexts[0]:callback(*args,**kwargs)else:callback(*args,**kwargs)if_state.contexts:return_StackContextWrapper(wrapped,fn,_state.contexts)else:return_StackContextWrapper(fn)

@contextlib.contextmanagerdef_nested(*managers):"""Support multiple context managers in a single with-statement. Copied from the python 2.6 standard library. It's no longer present in python 3 because the with statement natively supports multiple context managers, but that doesn't help if the list of context managers is not known until runtime. """exits=[]vars=[]exc=(None,None,None)try:formgrinmanagers:exit=mgr.__exit__enter=mgr.__enter__vars.append(enter())exits.append(exit)yieldvarsexcept:exc=sys.exc_info()finally:whileexits:exit=exits.pop()try:ifexit(*exc):exc=(None,None,None)except:exc=sys.exc_info()ifexc!=(None,None,None):# Don't rely on sys.exc_info() still containing# the right information. Another exception may# have been raised and caught by an exit methodraise_exc_info(exc)