# Source code for tornado.stack_context

    #
    # 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 state
    that follows execution as it moves to other execution contexts.

    The motivating examples are to eliminate the need for explicit
    ``async_callback`` wrappers (as in `tornado.web.RequestHandler`), and to
    allow some additional context to be kept for logging.

    This is slightly magic, but it's an extension of the idea that an
    exception handler is a kind of stack-local state and when that stack
    is suspended and resumed in a new context that state needs to be
    preserved.  `StackContext` shifts the burden of restoring that state
    from each call site (e.g.  wrapping each `.AsyncHTTPClient` callback
    in ``async_callback``) to the mechanisms that transfer control from
    one context to another (e.g. `.AsyncHTTPClient` itself, `.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 descendants*
            # will cause the process to exit instead of spinning endlessly
            # in the ioloop.
            http_client.fetch(url, callback)
        ioloop.start()

    Most applications shouldn'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`.

    .. deprecated:: 5.1

       The ``stack_context`` package is deprecated and will be removed in
       Tornado 6.0.
    """

    from __future__ import absolute_import, division, print_function

    import sys
    import threading
    import warnings

    from tornado.util import raise_exc_info

    class StackContextInconsistentError(Exception):
        pass

    class _State(threading.local):
        def __init__(self):
            self.contexts = (tuple(), None)

    _state = _State()

    class StackContext(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):
            warnings.warn("StackContext is deprecated and will be removed in Tornado 6.0",
                          DeprecationWarning)
            self.context_factory = context_factory
            self.contexts = []
            self.active = True

        def _deactivate(self):
            self.active = False

        # StackContext protocol
        def enter(self):
            context = self.context_factory()
            self.contexts.append(context)
            context.__enter__()

        def exit(self, type, value, traceback):
            context = self.contexts.pop()
            context.__exit__(type, value, traceback)

        # 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
            self.new_contexts = (self.old_contexts[0] + (self,), self)
            _state.contexts = self.new_contexts

            try:
                self.enter()
            except:
                _state.contexts = self.old_contexts
                raise

            return self._deactivate

        def __exit__(self, type, value, traceback):
            try:
                self.exit(type, value, traceback)
            finally:
                final_contexts = _state.contexts
                _state.contexts = self.old_contexts

                # Generator coroutines and with-statements with non-local
                # effects interact badly.  Check here for signs of
                # the stack getting out of sync.
                # Note that this check comes after restoring _state.context
                # so that if it fails things are left in a (relatively)
                # consistent state.
                if final_contexts is not self.new_contexts:
                    raise StackContextInconsistentError(
                        'stack_context inconsistency (may be caused by yield '
                        'within a "with StackContext" block)')

                # Break up a reference to itself to allow for faster GC on CPython.
                self.new_contexts = None

    class ExceptionStackContext(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.

        .. versionadded:: 5.1

           The ``delay_warning`` argument can be used to delay the emission
           of DeprecationWarnings until an exception is caught by the
           ``ExceptionStackContext``, which facilitates certain transitional
           use cases.
        """
        def __init__(self, exception_handler, delay_warning=False):
            self.delay_warning = delay_warning
            if not self.delay_warning:
                warnings.warn(
                    "StackContext is deprecated and will be removed in Tornado 6.0",
                    DeprecationWarning)
            self.exception_handler = exception_handler
            self.active = True

        def _deactivate(self):
            self.active = False

        def exit(self, type, value, traceback):
            if type is not None:
                if self.delay_warning:
                    warnings.warn(
                        "StackContext is deprecated and will be removed in Tornado 6.0",
                        DeprecationWarning)
                return self.exception_handler(type, value, traceback)

        def __enter__(self):
            self.old_contexts = _state.contexts
            self.new_contexts = (self.old_contexts[0], self)
            _state.contexts = self.new_contexts

            return self._deactivate

        def __exit__(self, type, value, traceback):
            try:
                if type is not None:
                    return self.exception_handler(type, value, traceback)
            finally:
                final_contexts = _state.contexts
                _state.contexts = self.old_contexts

                if final_contexts is not self.new_contexts:
                    raise StackContextInconsistentError(
                        'stack_context inconsistency (may be caused by yield '
                        'within a "with StackContext" block)')

                # Break up a reference to itself to allow for faster GC on CPython.
                self.new_contexts = None

    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.
        """
        def __enter__(self):
            self.old_contexts = _state.contexts
            _state.contexts = (tuple(), None)

        def __exit__(self, type, value, traceback):
            _state.contexts = self.old_contexts

    def _remove_deactivated(contexts):
        """Remove deactivated handlers from the chain"""
        # Clean ctx handlers
        stack_contexts = tuple([h for h in contexts[0] if h.active])

        # Find new head
        head = contexts[1]
        while head is not None and not head.active:
            head = head.old_contexts[1]

        # Process chain
        ctx = head
        while ctx is not None:
            parent = ctx.old_contexts[1]

            while parent is not None:
                if parent.active:
                    break
                ctx.old_contexts = parent.old_contexts
                parent = parent.old_contexts[1]

            ctx = parent

        return (stack_contexts, head)

    def wrap(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).
        """
        # Check if function is already wrapped
        if fn is None or hasattr(fn, '_wrapped'):
            return fn

        # Capture current stack head
        # TODO: Any other better way to store contexts and update them in wrapped function?
        cap_contexts = [_state.contexts]

        if not cap_contexts[0][0] and not cap_contexts[0][1]:
            # Fast path when there are no active contexts.
            def null_wrapper(*args, **kwargs):
                try:
                    current_state = _state.contexts
                    _state.contexts = cap_contexts[0]
                    return fn(*args, **kwargs)
                finally:
                    _state.contexts = current_state
            null_wrapper._wrapped = True
            return null_wrapper

        def wrapped(*args, **kwargs):
            ret = None
            try:
                # Capture old state
                current_state = _state.contexts

                # Remove deactivated items
                cap_contexts[0] = contexts = _remove_deactivated(cap_contexts[0])

                # Force new state
                _state.contexts = contexts

                # Current exception
                exc = (None, None, None)
                top = None

                # Apply stack contexts
                last_ctx = 0
                stack = contexts[0]

                # Apply state
                for n in stack:
                    try:
                        n.enter()
                        last_ctx += 1
                    except:
                        # Exception happened. Record exception info and store top-most handler
                        exc = sys.exc_info()
                        top = n.old_contexts[1]

                # Execute callback if no exception happened while restoring state
                if top is None:
                    try:
                        ret = fn(*args, **kwargs)
                    except:
                        exc = sys.exc_info()
                        top = contexts[1]

                # If there was exception, try to handle it by going through the exception chain
                if top is not None:
                    exc = _handle_exception(top, exc)
                else:
                    # Otherwise take shorter path and run stack contexts in reverse order
                    while last_ctx > 0:
                        last_ctx -= 1
                        c = stack[last_ctx]

                        try:
                            c.exit(*exc)
                        except:
                            exc = sys.exc_info()
                            top = c.old_contexts[1]
                            break
                    else:
                        top = None

                    # If if exception happened while unrolling, take longer exception handler path
                    if top is not None:
                        exc = _handle_exception(top, exc)

                # If exception was not handled, raise it
                if exc != (None, None, None):
                    raise_exc_info(exc)
            finally:
                _state.contexts = current_state
            return ret

        wrapped._wrapped = True
        return wrapped

    def _handle_exception(tail, exc):
        while tail is not None:
            try:
                if tail.exit(*exc):
                    exc = (None, None, None)
            except:
                exc = sys.exc_info()

            tail = tail.old_contexts[1]

        return exc

    def run_with_stack_context(context, func):
        """Run a coroutine ``func`` in the given `StackContext`.

        It is not safe to have a ``yield`` statement within a ``with StackContext``
        block, so it is difficult to use stack context with `.gen.coroutine`.
        This helper function runs the function in the correct context while
        keeping the ``yield`` and ``with`` statements syntactically separate.

        Example::

            @gen.coroutine
            def incorrect():
                with StackContext(ctx):
                    # ERROR: this will raise StackContextInconsistentError
                    yield other_coroutine()

            @gen.coroutine
            def correct():
                yield run_with_stack_context(StackContext(ctx), other_coroutine)

        .. versionadded:: 3.1
        """
        with context:
            return func()