I recently started using twisted a couple of weeks ago. For those who don't know, twisted is "event-driven networking engine written in Python". The learning curve is pretty steep if you've never done asynchronous programming before. During the project I was working on, I ran into a condition where I thought I needed to restart the twisted reactor. According to everything I found online, restarting the reactor is not supported. But I can be stubborn so I tried to find a way anyway.
Let's start by creating a pretty standard twisted server. We'll subclass LineReceiver which basically makes a TCP server that accepts full lines of text, although it can also do raw data too. Let's take a look at the code:
from twisted.internet import reactor from twisted.protocols.basic import LineReceiver from twisted.internet.protocol import Factory PORT = 9000 class LineServer(LineReceiver): def connectionMade(self): """ Overridden event handler that is called when a connection to the server was made """ print "server received a connection!" def connectionLost(self, reason): """ Overridden event handler that is called when the connection between the server and the client is lost @param reason: Reason for loss of connection """ print "Connection lost" print reason def lineReceived(self, data): """ Overridden event handler for when a line of data is received from client @param data: The data received from the client """ print 'in lineReceived' print 'data => ' + data class ServerFactory(Factory): protocol = LineServer if __name__ == '__main__': factory = ServerFactory() reactor.listenTCP(PORT, factory) reactor.run()
All of the camel-cased methods are overridden twisted methods. I just stubbed them out and have them print to stdout whenever they get called. Now let's make a client that has a reactor that we restart a few times:
import time import twisted.internet from twisted.internet import reactor, protocol from twisted.protocols.basic import LineOnlyReceiver HOST = 'localhost' PORT = 9000 class Client: """ Client class wrapper """ def __init__(self, new_user): self.new_user = new_user self.factory = MyClientFactory() def connect(self, server_address=HOST): """ Connect to the server @param server_address: The server address """ reactor.connectTCP(server_address, PORT, self.factory, timeout=30) class MyProtocol(LineOnlyReceiver): def connectionMade(self): """ Overridden event handler that is fired when a connection is made to the server """ print "client connected!" self.run_long_running_process() def lineReceived(self, data): """ Gets the data from the server @param data: The data received from the server """ print "Received data: " + data def connectionLost(self, reason): """ Connection lost event handler @param reason: The reason the client lost connection with the server """ print "Connection lost" def run_long_running_process(self): """ Run the process """ print 'running process' time.sleep(5) print "process finished!" self.transport.write('finished' + '\r\n') reactor.callLater(5, reactor.stop) class MyClientFactory(protocol.ClientFactory): protocol = MyProtocol if __name__ == '__main__': # run reactor multiple times tries = 3 while tries: client = Client(new_user=True) client.connect('localhost') try: reactor.run() tries -= 1 print "tries " + str(tries) except Exception, e: print e import sys del sys.modules['twisted.internet.reactor'] from twisted.internet import reactor from twisted.internet import default default.install()
Here we create a simple client that accepts only lines of text too (LineOnlyReceiver). The magic for restarting the reactor lies in the while loop at the end of the code. I actually found the code in the exception handler inside of twisted's reactor.py file which gave me the idea. Basically what we're doing is importing Python's sys module. We then delete the reactor from sys.modules which allows us to re-port it and reinstall the default reactor. If you run the server in one terminal and the client in another, you can see the client reconnect three times.
As I mentioned at the beginning, I'm still pretty new to twisted. What you should probably do instead of restarting the reactor is run it in another thread. Or you might use one of its delayed calls or deferred threads to get around the "need" to restart the reactor. Frankly, the method in this article doesn't even work in some conditions. I was actually trying to restart the reactor once inside a function that was decorated with contextlib's contextmanager decorator and that somehow prevented this code to work correctly. Regardless, I thought it was an interesting way to reload a module.
Copyright © 2021 Mouse Vs Python | Powered by Pythonlibrary