Tornado: Blocking calls using @gen.coroutine

Created on 25 Aug 2014  路  7Comments  路  Source: tornadoweb/tornado

I am trying to write a simple, _blocking_ function my_recv_future that sends a ZMQ packet, and then returns when the response is received. The ZMQ library supports the Tornado ioloop, and its knowledge is not necessary for this example

I wrote this function:

@gen.coroutine
def my_recv_future(socket, amsg):
    zmqstream = ZMQStream(socket)  # Required for ZMQ
    future = Future()
    def _finish(reply):
        future.set_result(reply)
        zmqstream.close()
    zmqstream.on_recv(_finish)
    zmqstream.send(amsg)
    resp = yield future   #Block here waiting for the reply
    raise gen.Return(resp)

I then want to call this function as:

val = my_recv_future(socket, amsg) #Blocking call, wait till reply
# Do things with val

The problem is that since my_recv_future is a @gen.coroutine, it returns a Future, while I want it to return the actual value. When I try to block on val.result(), I get an error saying that DummyFuture doesn't support blocking. I tried installing concurrent.futures, but I still get the same error (I thought it was supposed to overwrite the supplied version).

How does one block on a Future? What is the right way to create a blocking function as in above?

Most helpful comment

There is one small wrinkle: If a class (including any of its superclasses) defines both __new__ and __init__, both will normally be called, but if __new__ is a coroutine __init__ will not be called. Other than that, it should work fine.

As a matter of style, however, I would prefer to use static factory functions which can easily be coroutines. Making __new__ a coroutine is a little too magical for my tastes (it's not too bad, but I would recommend that you spend your magic budget elsewhere).

All 7 comments

Hi, Tornado does not support blocking on a Future's result in the way that multithreaded applications do. Tornado processes are typically single-threaded, so if the current thread blocks waiting for a Future to be resolved, it will wait forever, since there is no other thread completing the work in the background.

Instead, you must allow the IOLoop to run while you wait for the Future to resolve. There are two ways. One, with callbacks:

IOLoop.current().add_future(val, callback)

"callback" will receive the Future once it is resolved, so the callback can call val.result(). See:

http://tornado.readthedocs.org/en/latest/ioloop.html#tornado.ioloop.IOLoop.add_future

The typical way to wait on a Future, however, is to make the _caller_ a coroutine, so it can yield the Future:

@gen.coroutine
def my_coro():
    val = yield my_recv_future(socket, msg)
    # Either an exception is thrown, or "val" contains result

See my article on refactoring with coroutines for more details on calling coroutines from coroutines.

Thanks a lot for the insight @ajdavis, you certainly made things clearer. I rewrote the snippets here:
http://nbviewer.ipython.org/gist/bbirand/8cfd8a32b2b5970b1e83

Yet, I'm still confused about this interaction of this function with the rest of my code. When I meant "blocking" in my question, I meant "blocking the coroutine", not the thread (similar to gevent.wait()). I feel like having blocking in this way on Futures would make designing the system more intuitive.

For instance, in my snippet, f() is also a coroutine, and therefore returns a future. So the function that is calling it also has to yield, which makes it also a coroutine. There seems to be this chicken-and-egg situation that I can't resolve. For instance, I want to have:

def __init__(self):
    self.val = f()

But this wouldn't work, since val would be a future. I can do yield f(), but then i also need to make __init__() into a coroutine, which is kinda ugly. What would be intuitive to write is:

def __init__(self):
    self.val = f().get()

Then the function blocks, while the ioloop is running other operations. When the ZMQ packet is received, the ioloop returns the value.

Is this possible in the current scheme? Does this even make sense?

Hi. Basically, no: the only way to block a coroutine is with a "yield" statement. There are technical reasons you can't do this in Python: generators only yield when they hit a "yield" statement, and Tornado coroutines are generators. Gevent uses greenlets to achieve the same goal but without explicit "yields". For a philosophical argument about why explicitly yielding is preferable, see this article by Twisted's author. It applies to Tornado just as well:

https://glyph.twistedmatrix.com/2014/02/unyielding.html

A consequence of this is, you must avoid I/O in constructors. If you want to use the value of a Future within an __init__ method, I suggest a creation-coroutine:

class Thing(object):
    def __init__(self, val):
        self.val = val

@gen.coroutine
def create_thing():
    val = yield f()
    return Thing(val)

I had struggled with the generator problem too, and one solution I hit upon to do this sort of thing is implemented in my library greenado, it uses greenlets to implement tornado-compatible coroutines. This effectively allows you to pseudo-block without a yield statement.

However, even with this solution, at _some_ point in your call stack there is a generator that has to be properly yielded, but you might be able to put it really high up in the stack so you don't notice it.

Obviously, as this uses greenlets, the philisophical bits referenced above about explicit yielding apply to this solution.

@ajdavis

A consequence of this is, you must avoid I/O in constructors.

Is there a problem if I do my I/O in __new__ instead of in __init__? I have done it before and I had no problems. But maybe I'm not aware of some detail.

For example I can do:

class DBObject(object):
    @coroutine
    def __new__(cls, _id):
        self = super().__new__()
        self._data = yield db.mycollection.find_one({'_id': _id})
        return self

@coroutine
def other_function():
    obj = yield DBObject(71253)

Wow, that's news to me! How interesting.

There is one small wrinkle: If a class (including any of its superclasses) defines both __new__ and __init__, both will normally be called, but if __new__ is a coroutine __init__ will not be called. Other than that, it should work fine.

As a matter of style, however, I would prefer to use static factory functions which can easily be coroutines. Making __new__ a coroutine is a little too magical for my tastes (it's not too bad, but I would recommend that you spend your magic budget elsewhere).

Was this page helpful?
0 / 5 - 0 ratings