В Tornado, как я могу увидеть Исключения, доступные в сопрограмме, названной PeriodicCallback?

Я написал программу, которая периодически вызывается сопрограммой coroutine из основного ioloop следующим образом:

from tornado import ioloop, web, gen, log
tornado.log.enable_pretty_printing()
import logging; logging.basicConfig()

@gen.coroutine
def callback():
    print 'get ready for an error...'
    raise Exception()
    result = yield gen.Task(my_async_func)

l = ioloop.IOLoop.instance()
cb = ioloop.PeriodicCallback(callback, 1000, io_loop=l)
cb.start
l.start()

Выход, который я получаю, просто:

$ python2 app.py
get ready for an error...
get ready for an error...
get ready for an error...
get ready for an error...

raise Exception() молча игнорируется! Если я изменю обратный вызов, просто

def callback():
    print 'get ready for an error...'
    raise Exception()

Я получаю полную трассировку стека, как я ожидаю (и нуждаюсь). Как я могу получить эту трассировку стека при использовании сопрограммы?

Ответ 1

@tornado.gen.coroutine возвращает функцию return tornado.concurrent.Future, поэтому вам не нужно вставлять ее в tornado.gen.Task, но вы можете вызвать ее с помощью ключевого слова yield:

@tornado.gen.coroutine
def inner():
    logging.info('inner')

@tornado.gen.coroutine
def outer():
    logging.info('outer')
    yield inner()

Исключение в функции, украшенной таким образом, заверяется в этот объект tornado.concurrent.Future, и его можно вернуть позже, используя его метод exception(). В вашем случае tornado.ioloop.PeriodicCallback вызывает ваш метод обратного вызова, после чего он просто отбрасывает возвращаемый объект tornado.concurrent.Future вместе с исключением, содержащимся в нем. Чтобы обнаружить исключение, вы можете использовать цепной вызов:

@tornado.gen.coroutine
def inner():
    raise Exception()

@tornado.gen.coroutine
def outer():
    try:
        yield inner()   
    except Exception, e:
        logging.exception(e)

Но в вашем случае на самом деле проще просто поймать его сразу после метания:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import tornado.gen
import tornado.ioloop
import tornado.options
import logging

tornado.options.parse_command_line()

@tornado.gen.coroutine
def callback():
    logging.info('get ready for an error...')
    try:
        raise Exception()   
    except Exception, e:
        logging.exception(e)

main_loop = tornado.ioloop.IOLoop.instance()
scheduler = tornado.ioloop.PeriodicCallback(callback, 1000, io_loop = main_loop)
scheduler.start()
main_loop.start()

@gen.engine не возвращает функцию a tornado.concurrent.Future, поэтому исключения не завертываются.

Ответ 2

Я не понимаю, почему именно, но изменение @gen.coroutine до @gen.engine позволяет исключению правильно воспламеняться. Он по-прежнему работает асинхронно.