Search code examples
python-2.7twisted

The first is disconnected only when second client connects in twisted


I have the following code of twisted server listening to clients

class Echo(LineReceiver):

    def connectionMade(self):
        self.factory.clients.append(self)
        self.setRawMode()
        self._peer = self.transport.getPeer()
        timerClass(self)

    def connectionLost(self, reason):
        self.factory.clients.remove(self)
        print 'Lost connection from', self._peer

def why(self):
    print str(self._peer) + "we"
    self.transport.abortConnection()

def timerClass(self):
    t = Timer(2.0, lambda:why(self))
    t.start()

According to my code the client should disconnect after 2 seconds after it connects to server. But the client doesn't disconnect after 2 seconds, it disconnect after 2nd client connects to server. Why is it happening like this, I am not bale to figure out.


Solution

  • The thing that jumps out at me is your Timer call, where is that from? It is timeit.Timer?

    The reason it jumps out at me is that Twisted's behavior goes all screwy if you block the reactor, and unless I'm mistaken that call is a blocking sleep. (I also find some refs to a threading timer call, which is also likely cause trouble in twisted because much of twisted isn't thread safe, so the following answer still applies)

    Generally in Twisted you want to use reactor.callLater delay method for this style of application. See: http://twistedmatrix.com/documents/current/core/howto/time.html

    I'm guessing that your code isn't really waiting for the next connection, but that the sleep is blocking the next connection until the timer runs out, at which time you see your second connection connect and your first connection die.

    Give reactor.callLater a try in place of your Timer call and see if that fixes your issue. If not update your question with more code/test-examples

    Update (... comment points out this problem was threading not timeit)

    Given many parts of Twisted aren't thread-safe (where "not safe" means anything could happen - the behavior isn't predicable), the following is only a guess, but I think its a plausible explanation of the odd behavior you were seeing:

    1. Your reactor.run sets up a select/epoll/which-ever-event-call-style-of-loop with just your comms file-descriptor registered to wake the event call and it goes to sleep.
    2. Your first connection comes in, which momentarily wakes twisted to service the connection, your timerClass is run, but twisted goes back to sleep, blissfully unaware of the thread. That means:
    3. When your threaded-sleep finishes and runs self.transport.abortConnection some vars get set that intend to shutdown the connection but the work behind those vars can't be executed until Twisted wakes back up (... my specifics might be particularly wrong here, I haven't dug through the abortConnection codebase to make sure this is the reality of that call)
    4. Everything is stuck until Twisted wakes back up, but that will only happen with an event, such as:
    5. You make a second connection, which wakes up the Twisted event call
    6. With Twisted awake again, the abortConnection process, which started in the threaded call can be completed, killing the first connection.

    The test for this theory would be to have the first connection send data, after the thread Timer ran transport.abortConnection. Receive data should wake Twisted, and following with my theory, should let the transport.abortConnection finish without requiring a second connection.

    With all that said, the fix is the same. Switch to Twisted's reactor.callLater, which registers a timer directly into Twisted's event-sleep.

    If your interested in a longer breakdown of Twisted vs threads see my answer on How to run a program and also execute code...