]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Avoid use of the @contextlib.contextmanager decorator.
authorBen Darnell <ben@bendarnell.com>
Fri, 10 Dec 2010 23:56:38 +0000 (15:56 -0800)
committerBen Darnell <ben@bendarnell.com>
Fri, 10 Dec 2010 23:56:38 +0000 (15:56 -0800)
This decorator has over 5x the overhead of a hand-written class
with __enter__ and __exit__ methods.

tornado/stack_context.py
tornado/web.py

index 0cada16ed0bfab275d0d7a0ee9d817627a2ea405..1ede824192ee95c2aeb55f606c87b489282361e7 100644 (file)
@@ -59,39 +59,69 @@ class _State(threading.local):
         self.contexts = ()
 _state = _State()
 
-@contextlib.contextmanager
-def StackContext(context_factory):
-    '''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):
+class StackContext(object):
+    def __init__(self, context_factory):
+        '''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):
+        '''
+        self.context_factory = context_factory
+
+    def __enter__(self):
+        self.old_contexts = _state.contexts
+        _state.contexts = self.old_contexts + (self.context_factory,)
+        try:
+            self.context = self.context_factory()
+            self.context.__enter__()
+        except Exception:
+            _state.contexts = self.old_contexts
+            raise
+
+    def __exit__(self, type, value, traceback):
+        try:
+            return self.context.__exit__(type, value, traceback)
+        finally:
+            _state.contexts = self.old_contexts
+
+def ExceptionStackContext(exception_handler):
+    '''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.
     '''
-    old_contexts = _state.contexts
-    try:
-        _state.contexts = old_contexts + (context_factory,)
-        with context_factory():
-            yield
-    finally:
-        _state.contexts = old_contexts
+    class Context(object):
+        def __enter__(self):
+            pass
+        def __exit__(self, type, value, traceback):
+            if type is not None:
+                return exception_handler(type, value, traceback)
+    return StackContext(Context)
 
-@contextlib.contextmanager
-def NullContext():
+class NullContext(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.
     '''
-    old_contexts = _state.contexts
-    try:
+    def __enter__(self):
+        self.old_contexts = _state.contexts
         _state.contexts = ()
-        yield
-    finally:
-        _state.contexts = old_contexts
+
+    def __exit__(self, type, value, traceback):
+        _state.contexts = self.old_contexts
 
 def wrap(fn):
     '''Returns a callable object that will resore the current StackContext
@@ -121,9 +151,12 @@ def wrap(fn):
         else:
             new_contexts = [StackContext(c)
                             for c in contexts[len(_state.contexts):]]
-        if new_contexts:
+        if len(new_contexts) > 1:
             with contextlib.nested(*new_contexts):
                 callback(*args, **kwargs)
+        elif new_contexts:
+            with new_contexts[0]:
+                callback(*args, **kwargs)
         else:
             callback(*args, **kwargs)
     if getattr(fn, 'stack_context_wrapped', False):
@@ -132,3 +165,4 @@ def wrap(fn):
     result = functools.partial(wrapped, fn, contexts)
     result.stack_context_wrapped = True
     return result
+
index d2fccdd908065fd8b9c415fbc0608b6ed2c76d5a..a4eb3f51550c2431d7c3b9c9c5a6f8c8edf3275f 100644 (file)
@@ -809,17 +809,15 @@ class RequestHandler(object):
     def reverse_url(self, name, *args):
         return self.application.reverse_url(name, *args)
 
-    @contextlib.contextmanager
-    def _stack_context(self):
-        try:
-            yield
-        except Exception, e:
-            self._handle_request_exception(e)
+    def _stack_context_handle_exception(self, type, value, traceback):
+        self._handle_request_exception(value)
+        return True
 
     def _execute(self, transforms, *args, **kwargs):
         """Executes this request with the given output transforms."""
         self._transforms = transforms
-        with stack_context.StackContext(self._stack_context):
+        with stack_context.ExceptionStackContext(
+            self._stack_context_handle_exception):
             if self.request.method not in self.SUPPORTED_METHODS:
                 raise HTTPError(405)
             # If XSRF cookies are turned on, reject form submissions without