Search code examples
pythonexceptiontornado

Python Tornado gen.engine exception handling


I am using Tornado 2.4, and I am trying to integrate async call. Lets say I need to access to a remote resource through a HTTP call, so I made this function in a tornado.web.RequestHandler:

@tornado.web.asynchronous 
def get(self, *args):
    try:
        self.remote_call()
        return 'OK'
    except Exception, e:
        self.handle_exception(e)

@gen.engine
def remote_call(self):
    http_client = httpclient.AsyncHTTPClient()
    response = yield gen.Task(http_client.fetch, 'http://google.com')
    self.process(response)

So my problem is, since remote_call is yielding a Task, it will obviously exit the remote_call function and continue the get function. Then when my task is complete, the engine will process the response. But if an error happen in the self.process(response), it will not be catch by my except, since this part of the code is not actually called here, but inside the engine where I do have no control.

So my question is, can I have some control on this engine? Can I handle error, can I ask to perform some specific task at the end the function?

I could do this directly in the function like this

@tornado.web.asynchronous 
def get(self, *args):
    self.remote_call()
    return 'OK'

@gen.engine
def remote_call(self):
    http_client = httpclient.AsyncHTTPClient()
    response = yield gen.Task(http_client.fetch, 'http://google.com')
    try:
        self.process(response)
    except:
        self.handle_exception(e)

But I want to make the handle exception generic and not copy pasting this on every of my Handler.

So do I have a way to access to the engine of Tornado? Note that I am using Tornado 2.4 but I can migrate to 3.0 if needed. Thanks


Solution

  • You can handle it in 2.4 by decorating your get call with @gen.engine, wrapping the call to self.remote_call in a gen.Task, and then yielding from that:

    @tornado.web.asynchronous
    @gen.engine
    def get(self, *args):
        try:
            yield gen.Task(self.remote_call)
        except Exception, e:
            self.handle_exception(e)
        self.finish()  # Make sure you call this when `get` is asynchronous.
    
    @gen.engine
    def remote_call(self):
        http_client = httpclient.AsyncHTTPClient()
        response = yield gen.Task(http_client.fetch, 'http://google.com')
        self.process(response)
    

    This will allow you to handle the exception in get, though you'll still see a traceback from the exception being raise in remote_call.

    However, I highly recommend you upgrade. Tornado is now on version 4.0. With 3.0 or later, you can use gen.coroutine instead of gen.engine and web.asynchronous:

    @gen.coroutine 
    def get(self, *args):
        try:
            yield self.remote_call()
        except Exception, e:
            self.handle_exception(e)
        self.finish()
    
    @gen.coroutine
    def remote_call(self):
        http_client = httpclient.AsyncHTTPClient()
        response = yield http_client.fetch('http://google.com')
        self.process(response)
    

    coroutine properly supresses the traceback from any exception thrown in remote_call, as well as letting you handle it in get.