Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference in tornado.gen.engine v/s tornado.gen.coroutine

Going by the tornado.gen documentation can someone help me understand the exact difference between tornado.gen.coroutine and tornado.gen.engine

like image 928
jdk2588 Avatar asked May 06 '14 18:05

jdk2588


People also ask

What is Tornado Coroutine?

Coroutines use the Python await keyword to suspend and resume execution instead of a chain of callbacks (cooperative lightweight threads as seen in frameworks like gevent are sometimes called coroutines as well, but in Tornado all coroutines use explicit context switches and are called as asynchronous functions).

What Gen is tornado?

Originally named the Tornado GR1 the aircraft's first use in live operations was during the Gulf War in 1991, when 60 Tornado GR1s were deployed from bases in Saudi Arabia and Bahrain. Later they were upgraded to the GR4 model, which has been used ever since over the skies of Kosovo, Afghanistan, Iraq and Syria.


1 Answers

As the tornado documentation for gen.engine says:

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

And as the gen.coroutine documentation says

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

gen.engine is basically an older, less streamlined version of what coroutine does. If you're writing new code, you should follow the documentation's advice and always use tornado.gen.coroutine.

It's pretty evident if you look at the code for both functions (with documentation stripped out).

engine:

def engine(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        runner = None

        def handle_exception(typ, value, tb):
            if runner is not None:
                return runner.handle_exception(typ, value, tb)
            return False
        with ExceptionStackContext(handle_exception) as deactivate:
            try:
                result = func(*args, **kwargs)
            except (Return, StopIteration) as e:
                result = getattr(e, 'value', None)
            else:
                if isinstance(result, types.GeneratorType):
                    def final_callback(value):
                        if value is not None:
                            raise ReturnValueIgnoredError(
                                "@gen.engine functions cannot return values: "
                                "%r" % (value,))
                        assert value is None
                        deactivate()
                    runner = Runner(result, final_callback)
                    runner.run()
                    return
            if result is not None:
                raise ReturnValueIgnoredError(
                    "@gen.engine functions cannot return values: %r" %
                    (result,))
            deactivate()
            # no yield, so we're done
    return wrapper

coroutine:

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

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

        def handle_exception(typ, value, tb):
            try:
                if runner is not None and runner.handle_exception(typ, value, tb):
                    return True
            except Exception:
                typ, value, tb = sys.exc_info()
            future.set_exc_info((typ, value, tb))
            return True
        with ExceptionStackContext(handle_exception) as deactivate:
            try:
                result = func(*args, **kwargs)
            except (Return, StopIteration) as e:
                result = getattr(e, 'value', None)
            except Exception:
                deactivate()
                future.set_exc_info(sys.exc_info())
                return future
            else:
                if isinstance(result, types.GeneratorType):
                    def final_callback(value):
                        deactivate()
                        future.set_result(value)
                    runner = Runner(result, final_callback)
                    runner.run()
                    return future
            deactivate()
            future.set_result(result)
        return future
    return wrapper

Both of these are probably pretty hard to understand at first glance. But still, it's obvious that the code is very similar, except that @gen.coroutine has some special handling of the callback kwarg, and it builds/returns a Future. @gen.engine has some code that specifically throws an error if you try to return something from it, rather than putting it in the Future.

like image 180
dano Avatar answered Oct 12 '22 23:10

dano