weizhao
weizhao

Reputation: 181

tornado finish() called twice

I write a comet demo with tornado and tornadoredis, demo can work but sometimes error happends, I have no idea how to fix it. anyone can help me ?

Error :

[E 150410 18:18:44 web:1421] Uncaught exception GET /comet?channelKey=channel%3Awx4g1nej7fuu&message_id=193 (127.0.0.1)
    HTTPServerRequest(protocol='http', host='tornado.test.com', method='GET', uri='/comet?channelKey=channel%3Awx4g1nej7fuu&message_id=193', version='HTTP/1.1', remote_ip='127.0.0.1', headers={'Remote-Addr': 'xxx', 'Service': 'android', 'X-Forwarded-For': 'xxx', 'User-Agent': 'Apache-HttpClient/UNAVAILABLE (java 1.4)', 'Host': 'tornado.test.wolonge.com', 'X-Requested-With': 'XMLHttpRequest', 'X-Real-Ip': 'xxx', 'Cookie': 'logintoken=gsvimqqefr8f9duu66emjrbbe5_8be6AsNqW%2B3kwH7OsnT0OAKbZNNqnzabDIVcFDE8TvyozQ7h'})
    Traceback (most recent call last):
      File "/usr/lib64/python2.6/site-packages/tornado/web.py", line 1302, in _stack_context_handle_exception
        raise_exc_info((type, value, traceback))
      File "/usr/lib64/python2.6/site-packages/tornado/web.py", line 1489, in wrapper
        result = method(self, *args, **kwargs)
      File "/home/wwwroot/wolongge_mobile/app/tornado_push/models/ws_handle.py", line 53, in ready_finish
        self.finish(res)
      File "/usr/lib64/python2.6/site-packages/tornado/web.py", line 863, in finish
        raise RuntimeError("finish() called twice.  May be caused "
    RuntimeError: finish() called twice.  May be caused by using async operations without the @asynchronous decorator

And my code is here:

class GroupChat(tornado.web.RequestHandler):
    def initialize(self):
        print 'GroupChat here'
        self.c = tornadoredis.Client(host=CONFIG['REDIS_HOST'], port=CONFIG['REDIS_PORT'], password=CONFIG['REDIS_AUTH'])
    @tornado.gen.coroutine
    @tornado.web.asynchronous
    def get(self):
        try:
            self.key = self.get_argument('channelKey')
            # print 'key:%s' % self.key
            self.key = url_unescape(self.key);
            # print 'key:%s' % self.key
            if(self.key):
                yield tornado.gen.Task(self.c.subscribe, self.key)
                self.c.listen(self.on_message)
        except Exception, e:
            self.c.disconnect()
            self.ready_finish('Bad Request (Missing argument)')
            print e
        pass


    @tornado.web.asynchronous
    def on_message(self, msg):
        if (msg.kind == 'message'):
            print msg
            message_id = int(self.get_argument('message_id'))
            max_message_id = int(msg.body)
            if(message_id < max_message_id):
                self.ready_finish('1')
            else:
                self.ready_finish('0')
        elif (msg.kind == 'unsubscribe'):
            self.c.disconnect()

    @tornado.web.asynchronous
    def ready_finish(self, res):
        if (self.c.subscribed):
            self.c.unsubscribe(self.key)
        self.finish(res)


    def on_connection_close(self):
        print 'on_connection_close here'

another ques, @tornado.web.asynchronous is the right way to use ? every method has @tornado.web.asynchronous ...

Upvotes: 1

Views: 2743

Answers (3)

BenJLI
BenJLI

Reputation: 730

This decorator should only be applied to the HTTP verb methods; its behavior is undefined for any other method. This decorator does not make a method asynchronous; it tells the framework that the method is asynchronous. For this decorator to be useful the method must (at least sometimes) do something asynchronous.

Upvotes: 0

Ben Darnell
Ben Darnell

Reputation: 22154

In general, avoid mixing the coroutine and asynchronous callback styles. Here, your get() method is a coroutine which will finish the request automatically when it returns, but it has also started the callback-based listen() which will try to finish the request later.

You need to either add some sort of coordination to ensure that the get() coroutine does not return until the callback has finished the request (a toro.Event is good for this) or get rid of the yield gen.Task and use an explicit callback when subscribing. In either case, you should only have one of @asynchronous or @coroutine on the get() method, and no decorators on the others.

Upvotes: 2

Serafim Suhenky
Serafim Suhenky

Reputation: 2160

You need @tornado.web.asynchronous only for get method. Also remove @tornado.gen.coroutine under get method.

As stated in docs for @tornado.web.asynchronous http://tornado.readthedocs.org/en/latest/web.html#tornado.web.asynchronous

This decorator should only be applied to the HTTP verb methods; its behavior is undefined for any other method. This decorator does not make a method asynchronous; it tells the framework that the method is asynchronous.

So you can't use it for on_message and ready_finish, remove it. For on_message and ready_finish you could use @tornado.gen.coroutine if you have any async calls there.

Upvotes: 0

Related Questions