1

I'm trying to write a longpolling server with Twisted, but I'm afraid I'm not understanding the request management.

When the client navigates away from the page, I can see it continue to loop in the console. I thought the instance would just destroy itself when the client disconnects.

And that is my goal, I want to run the logic in DataService individually for each client that connects.

Here's what I have: (i chopped this code down for easy consumption, might be some syntax errors because of that, but it came from working code)

class DataService(Resource):
    def __init__(self, sid):
        # set looping call
        self.loopingCall = task.LoopingCall(self.__print_data)
        self.loopingCall.start(5, False)
        Resource.__init__(self)
        self.sid = int(sid);
        self.finished = False

        # initialize response
        self.response = response = {'status':1, 'message': 'OK', 'time':int(time.time()), 'data': {}}

    def connectionLost(self, reason):
        self.loopingCall.stop();

    def render_GET(self, request):
        # response will be json format
        request.setHeader('Content-Type', 'application/json')
        # make sure required GET vars exist
        if 'lastupdate' not in request.args:
            self.loopingCall.stop()
            return ujson.dumps({'status':0,'message':'invalid query','data':{}})

        # set last update timestamp from query string
        self.lastupdate = int(request.args['lastupdate'][0])

        # set self.request so we can access it in __print_data
        self.request = request
        # call print data
        self.__print_data()

        if not self.finished:
            return server.NOT_DONE_YET

    def __print_data(self):

        # set updated data
        if self.lastupdate < self.myappobj.lastupdate
            self.response{'data']['items'] = {'foo':'bar'}

        # if updates were found, close loop, print, and finish request
        if len(response['data']) > 0:
            self.loopingCall.stop()
            self.request.write(self.jsonpcallback+'('+ujson.dumps(response)+')')
            self.request.finish()
            self.finished = True


class DataServer(Resource):
    def getChild(self, sid, request):
        return DataService(sid)
Coder1
  • 13,139
  • 15
  • 59
  • 89

1 Answers1

1

When the client navigates away from the page, I can see it continue to loop in the console. I thought the instance would just destroy itself when the client disconnects.

It doesn't. First, instances are only destroyed when they are no longer "reachable" (just like any other kind of object you might have).

When you do this:

self.loopingCall = task.LoopingCall(self.__print_data)

You're creating a reference to self.__print_data, which has a reference to self. The LoopingCall then registers itself with the reactor (when you start it). So now the reactor has an indirect reference to self. So self will live forever, or until something changes.

You can get the LoopingCall to unregister itself from the reactor by stopping it. Once the reactor no longer refers to the LoopingCall, it will no longer be keeping the DataService instance (self) alive.

If you want to do this when the client closes the connection, then you want to use Request.notifyFinish. This is covered in the Twisted documentation, but it's pretty straightforward. You should do something like this:

request.notifyFinish().addErrback(lambda ignored: self.loopingcall.stop())

It's an errback because you only care about the case where the client closes the connection, not the case where you close the connection.

Jean-Paul Calderone
  • 47,755
  • 6
  • 94
  • 122