[Twisted-web] Response hanging after headers sent

exarkun at twistedmatrix.com exarkun at twistedmatrix.com
Thu Sep 11 09:21:26 MDT 2014


On 02:27 pm, bruno at distributedmatter.net wrote:
>Hello,
>
>I've been trying to implement a simple notification system via long 
>polling
>using Twisted 14.0.0. On the client side, a web page makes Ajax calls 
>to an
>"update" resource, which will deliver the latest status message known 
>to
>the application. On the server side, a thread feeds a queue with status
>events and another one polls this queue and sends the latest value to 
>any
>pending request, trying to keep the request open for 30 seconds, or 
>until a
>new status event arrives, whichever happens sooner (this is the long
>polling).
>
>[snip]
>
>class CounterUpdateResource(Resource):
>    def __init__(self, msg_queue):
>        Resource.__init__(self)
>        self.msg_queue = msg_queue
>        self.msg = {}
>        self.etag = None
>        self.pending_requests = []
>
>        t = threading.Thread(target=self._poll_messages)
>        t.daemon = True
>        t.start()
>
>    def _poll_messages(self):
>        while True:
>            try:
>                self.msg = self.msg_queue.get(True, 30)
>                self.etag = '"%s"' % (uuid.uuid4(),)
>                print "-> Got new message: %s" % (self.msg,)
>            except Queue.Empty:
>                pass
>            json_str = json.dumps(self.msg)
>            json_len = len(json_str)
>            while True:
>                try:
>                    request = self.pending_requests.pop()
>                    self._actual_render(request, json_str, json_len)
>                except IndexError:
>                    break
>
>    def _actual_render(self, request, json_msg, json_len):
>        try:
>            request.setETag(self.etag)
>            request.setHeader("Content-Type", "application/json")
>            request.setHeader("Content-Length", json_len)
>            request.write(json_msg)
>            request.finish()
>        except:
>            pass

`_poll_messages` is run in a thread.  It calls `_actual_render` - so 
`_actual_render` is run in a thread.  It calls these `request` methods - 
so they are run in a thread.

Twisted APIs are not thread-safe - except for one or two that are 
explicitly marked as being thread-safe.

Since this code calls Twisted APIs (the request methods) in a thread, 
they have undefined behavior.

If you change this so that `_actual_render` (or all of the request 
methods) are called in the reactor thread then I expect it will start to 
work reliably.

Stepping back, you might consider getting rid of the multithreaded in- 
process polling and instead make your application fully event-driven. 
Instead of having one part of your program put messages into a queue and 
another part look at the queue from time to time and do work if it finds 
an item, have one part of your program call a method implemented by 
another part of your program which does the work.  No threads, no 
polling, hooray.

Stepping back even further, there are existing libraries for doing this 
kind of thing with Twisted already.  There's Divmod Nevow's Athena and 
there's Minerva.  There are also other approaches to "real time" web - 
such as websockets (see Autobahn and txwebsocket).  You may not actually 
have to implement this low-level plumbing yourself.

Jean-Paul



More information about the Twisted-web mailing list