New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Understanding Tornado #12

Closed
dylanninin opened this Issue Aug 8, 2016 · 7 comments

Comments

Projects
None yet
1 participant

@dylanninin dylanninin added this to the Sprint 2 milestone Aug 8, 2016

@dylanninin dylanninin self-assigned this Aug 8, 2016

@dylanninin

This comment has been minimized.

Copy link
Owner Author

dylanninin commented Aug 18, 2016

@dylanninin

This comment has been minimized.

Copy link
Owner Author

dylanninin commented Aug 18, 2016

@dylanninin

This comment has been minimized.

Copy link
Owner Author

dylanninin commented Aug 18, 2016

@dylanninin

This comment has been minimized.

@dylanninin dylanninin removed the todo label Aug 18, 2016

@dylanninin

This comment has been minimized.

Copy link
Owner Author

dylanninin commented Aug 26, 2016

Tornado Coroutines and Concurrency

1. Future: https://github.com/tornadoweb/tornado/blob/master/tornado/concurrent.py#L132

A Placeholder for an asynchronous result.

A Future encapsulates the result of an asynchronous
operation. In synchronous applications Futures are used
to wait for the result from a thread or process pool; in
Tornado they are normally used with .IOLoop.add_future or by
yielding them in a .gen.coroutine.

tornado.concurrent.Future is similar to
concurrent.futures.Future, but not thread-safe (and therefore
faster for use with single-threaded event loops).

2. web.asynchronous: https://github.com/tornadoweb/tornado/blob/master/tornado/web.py#L1575

Wrap request handler methods with this if they are asynchronous.
This decorator is for callback-style asynchronous methods; for
coroutines, use the @gen.coroutine decorator without
@asynchronous. (It is legal for legacy reasons to use the two
decorators together provided @asynchronous is first, but
@asynchronous will be ignored in this case)

This decorator should only be applied to the :ref:HTTP verb methods <verbs>; its behavior is undefined for any other method.
This decorator does not make a method asynchronous; it tells
the framework that the method is asynchronous. For this decorator
to be useful the method must (at least sometimes) do something
asynchronous.

If this decorator is given, the response is not finished when the
method returns. It is up to the request handler to call
self.finish() <RequestHandler.finish> to finish the HTTP
request. Without this decorator, the request is automatically
finished when the get() or post() method returns

def asynchronous(method):
    from tornado.ioloop import IOLoop

    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        self._auto_finish = False
        with stack_context.ExceptionStackContext(
                self._stack_context_handle_exception):
            result = method(self, *args, **kwargs)
            if result is not None:
                result = gen.convert_yielded(result)

                # If @asynchronous is used with @gen.coroutine, (but
                # not @gen.engine), we can automatically finish the
                # request when the future resolves.  Additionally,
                # the Future will swallow any exceptions so we need
                # to throw them back out to the stack context to finish
                # the request.
                def future_complete(f):
                    f.result()
                    if not self._finished:
                        self.finish()
                IOLoop.current().add_future(result, future_complete)
                # Once we have done this, hide the Future from our
                # caller (i.e. RequestHandler._when_complete), which
                # would otherwise set up its own callback and
                # exception handler (resulting in exceptions being
                # logged twice).
                return None
            return result
    return wrapper

3. gen.coroutine: https://github.com/tornadoweb/tornado/blob/master/tornado/gen.py#L210

Decorator for asynchronous generators.

Any generator that yields objects from this module must be wrapped
in either this decorator or engine.

Coroutines may "return" by raising the special exception
Return(value) <Return>. In Python 3.3+, it is also possible for
the function to simply use the return value statement (prior to
Python 3.3 generators were not allowed to also return values).
In all versions of Python a coroutine that simply wishes to exit
early may use the return statement without a value.

Functions with this decorator return a .Future. Additionally,
they may be called with a callback keyword argument, which
will be invoked with the future's result when it resolves. If the
coroutine fails, the callback will not be run and an exception
will be raised into the surrounding .StackContext. The
callback argument is not visible inside the decorated
function; it is handled by the decorator itself.

From the caller's perspective, @gen.coroutine is similar to
the combination of @return_future and @gen.engine.

def coroutine(func, replace_callback=True):
    return _make_coroutine_wrapper(func, replace_callback=True)

4. gen.engine: https://github.com/tornadoweb/tornado/blob/master/tornado/gen.py#L175

Callback-oriented decorator for asynchronous generators.

This is an older interface; for new code that does not need to be
compatible with versions of Tornado older than 3.0 the
coroutine decorator is recommended instead.

This decorator is similar to coroutine, except it does not
return a .Future and the callback argument is not treated
specially.

In most cases, functions decorated with engine should take
a callback argument and invoke it with their result when
they are finished. One notable exception is the
~tornado.web.RequestHandler :ref:HTTP verb methods <verbs>,
which use self.finish() in place of a callback argument.

def engine(func):
    func = _make_coroutine_wrapper(func, replace_callback=False)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        future = func(*args, **kwargs)

        def final_callback(future):
            if future.result() is not None:
                raise ReturnValueIgnoredError(
                    "@gen.engine functions cannot return values: %r" %
                    (future.result(),))
        # The engine interface doesn't give us any way to return
        # errors but to raise them into the stack context.
        # Save the stack context here to use when the Future has resolved.
        future.add_done_callback(stack_context.wrap(final_callback))
    return wrapper

5. gen._make_coroutine_wrapper: https://github.com/tornadoweb/tornado/blob/master/tornado/gen.py#L248

The inner workings of @gen.coroutine and @gen.engine.
The two decorators differ in their treatment of the callback
argument, so we cannot simply implement @engine in terms of
@coroutine

def _make_coroutine_wrapper(func, replace_callback):
    # On Python 3.5, set the coroutine flag on our generator, to allow it
    # to be used with 'await'.
    if hasattr(types, 'coroutine'):
        func = types.coroutine(func)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        future = TracebackFuture()

        if replace_callback and 'callback' in kwargs:
            callback = kwargs.pop('callback')
            IOLoop.current().add_future(
                future, lambda future: callback(future.result()))

        try:
            result = func(*args, **kwargs)
        except (Return, StopIteration) as e:
            result = _value_from_stopiteration(e)
        except Exception:
            future.set_exc_info(sys.exc_info())
            return future
        else:
            if isinstance(result, GeneratorType):
                # Inline the first iteration of Runner.run.  This lets us
                # avoid the cost of creating a Runner when the coroutine
                # never actually yields, which in turn allows us to
                # use "optional" coroutines in critical path code without
                # performance penalty for the synchronous case.
                try:
                    orig_stack_contexts = stack_context._state.contexts
                    yielded = next(result)
                    if stack_context._state.contexts is not orig_stack_contexts:
                        yielded = TracebackFuture()
                        yielded.set_exception(
                            stack_context.StackContextInconsistentError(
                                'stack_context inconsistency (probably caused '
                                'by yield within a "with StackContext" block)'))
                except (StopIteration, Return) as e:
                    future.set_result(_value_from_stopiteration(e))
                except Exception:
                    future.set_exc_info(sys.exc_info())
                else:
                    Runner(result, future, yielded)
                try:
                    return future
                finally:
                    # Subtle memory optimization: if next() raised an exception,
                    # the future's exc_info contains a traceback which
                    # includes this stack frame.  This creates a cycle,
                    # which will be collected at the next full GC but has
                    # been shown to greatly increase memory usage of
                    # benchmarks (relative to the refcount-based scheme
                    # used in the absence of cycles).  We can avoid the
                    # cycle by clearing the local variable after we return it.
                    future = None
        future.set_result(result)
        return future
    return wrapper

6. gen.Runner: https://github.com/tornadoweb/tornado/blob/master/tornado/gen.py#L937

Internal implementation of tornado.gen.engine.
Maintains information about pending callbacks and their results.
The results of the generator are stored in result_future (a
.TracebackFuture)

# Simplified inner loop of tornado.gen.Runner
def run(self):
    # send(x) makes the current yield return x.
    # It returns when the next yield is reached
    future = self.gen.send(self.next)
    def callback(f):
        self.next = f.result()
        self.run()
    future.add_done_callback(callback)

7.async/await support: http://www.tornadoweb.org/en/stable/guide/coroutines.html#native-coroutines

Python 3.5 introduces the async and await keywords (functions using these keywords are also called “native coroutines”). Starting in Tornado 4.3, you can use them in place of yield-based coroutines. Simply use async def foo() in place of a function definition with the @gen.coroutine decorator, and await in place of yield. The rest of this document still uses the yield style for compatibility with older versions of Python, but async and await will run faster when they are available:

async def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = await http_client.fetch(url)
    return response.body

The await keyword is less versatile than the yield keyword. For example, in a yield-based coroutine you can yield a list of Futures, while in a native coroutine you must wrap the list in tornado.gen.multi. You can also use tornado.gen.convert_yielded to convert anything that would work with yield into a form that will work with await.

While native coroutines are not visibly tied to a particular framework (i.e. they do not use a decorator like tornado.gen.coroutine or asyncio.coroutine), not all coroutines are compatible with each other. There is a coroutine runner which is selected by the first coroutine to be called, and then shared by all coroutines which are called directly with await. The Tornado coroutine runner is designed to be versatile and accept awaitable objects from any framework; other coroutine runners may be more limited (for example, the asyncio coroutine runner does not accept coroutines from other frameworks). For this reason, it is recommended to use the Tornado coroutine runner for any application which combines multiple frameworks. To call a coroutine using the Tornado runner from within a coroutine that is already using the asyncio runner, use the tornado.platform.asyncio.to_asyncio_future adapter.

Reference

@dylanninin dylanninin added the python label Dec 2, 2016

@dylanninin dylanninin closed this Jan 22, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment