Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Python 3.5 style async and await in Tornado for websockets?

Consider this short snippet:

import tornado
import tornado.websocket
import tornado.ioloop
import tornado.gen
import tornado.web

class NewWsHandler(tornado.websocket.WebSocketHandler):
    async def on_message(self, message):
        await self.write_message("echo " + message)

class OldWsHandler(tornado.websocket.WebSocketHandler):
    @tornado.gen.coroutine
    def on_message(self, message):
        yield self.write_message("echo " + message)

app = tornado.web.Application([(r'/', OldWsHandler)])
app.listen(8080)
tornado.ioloop.IOLoop.current().start()

OldWsHandler uses the pre-3.5 way of doing asynchronous functions in Tornado, and it works fine. However, as the documentation states, it is preferred to use PEP 0492 for readability and speed.

The documentation says:

Simply use async def foo() in place of a function definition with the @gen.coroutine decorator, and await in place of yield.

So I wrote NewWsHandler. However, when sending a websocket message, it raises a warning:

/usr/lib/python3.5/site-packages/tornado/websocket.py:417: RuntimeWarning: coroutine 'on_message' was never awaited
  callback(*args, **kwargs)

I don't really know how to (properly) fix it. I tried decorating it in tornado.web.asynchronous, but that assumes a HTTP verb method. So after I override finish() (websockets are not allowed to do that), it seems to be kind of working:

class NewWsHandler(tornado.websocket.WebSocketHandler):
    def finish(self):
        pass

    @tornado.web.asynchronous
    async def on_message(self, message):
        await self.write_message("echo " + message)

But this still looks hackerish, and seems to be contradicting the documentation. What is the right way of doing this?

Note: I am using Python 3.5.1 and Tornado 4.3.

like image 798
Evert Heylen Avatar asked Feb 21 '16 22:02

Evert Heylen


1 Answers

Coroutines are called differently than regular functions; therefore when subclassing and overriding methods you cannot change a regular method in the base class into a coroutine in your subclass (unless the base class specifically says this is OK). WebSocketHandler.on_message may not be a coroutine (as of Tornado 4.3; this may change in the future).

Instead, if you need to do something asynchronous in response to a message, put the asynchronous parts in a separate function and call it with IOLoop.current().spawn_callback. (or if write_message is the only async thing you're doing, just call it synchronously)


Update

This changed in Tornado 4.5, WebSocketHandler.on_message can now be used as a coroutine. See http://www.tornadoweb.org/en/stable/releases/v4.5.0.html#tornado-websocket.

like image 177
Ben Darnell Avatar answered Nov 15 '22 08:11

Ben Darnell