tornado: No way to catch exceptions when calling coroutine from gen.Task

from tornado import gen
from tornado.ioloop import IOLoop

@gen.coroutine
def throw():
    10/0 # Exception here
    return 'hello'


@gen.coroutine
def test():
    print "i'm ok"
    res = yield gen.Task(throw)
    print "here too" # it is never executed


test()

IOLoop.instance().start()

There must be a way to catch exceptions.

About this issue

  • Original URL
  • State: closed
  • Created 11 years ago
  • Comments: 15 (9 by maintainers)

Most upvoted comments

@kanski you could catch the exception like so:

@gen.coroutine
def test():
    print "i'm ok"
    try:
        res = yield gen.Task(throw)
    except Exception, e:
        print 'EXCEPTION!', e
    print "here too"

This prints:

i'm ok
EXCEPTION! integer division or modulo by zero
here too

A few corrections to your code, by the way. Since all coroutines return Futures, you can omit gen.Task and have the same outcome:

@gen.coroutine
def test():
    print "i'm ok"
    try:
        res = yield throw()
    except Exception, e:
        print 'EXCEPTION!', e
    print "here too"

Also, if you ever put a yield statement in throw() then the return 'hello' will no longer be valid: you’ll get a SyntaxError: 'return' with argument inside generator in Pythons older than 3.3. Better to use raise gen.Return('hello').

So, the real question you’re asking is, I think, what happened to the exception? and the answer is, it’s wrapped in the Future returned by test(). If you do not add a try / except in test, you can do this to catch the exception instead:

def done_callback(future):
    future.result()

future = test()
future.add_done_callback(done_callback)

IOLoop.instance().start()

If you run this code you’ll get a lengthy traceback ending in ZeroDivisionError.

I agree that this is a confusing consequence of using gen.coroutine in a command-line application like the one you’re writing. If you were using coroutines in RequestHandlers for a web application, Tornado would log or display the error.

The method IOLoop.run_sync exists to help with this situation: IOLoop.instance().run_sync(test) is equivalent to the block above with future.add_done_callback.