Je me suis un peu amusé avec le serveur Web Tornado et je suis arrivé à un point où je veux arrêter le serveur Web (par exemple lors de tests d'unités). L'exemple simple suivant existe sur la page Web Tornado :
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __== "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Une fois que tornado.ioloop.IOLoop.instance().start()
est appelé, il bloque le programme (ou le thread actuel). La lecture du code source de l’objet IOLoop
donne cet exemple dans la documentation de la fonction stop
:
To use asynchronous methods from otherwise-synchronous code (such as
unit tests), you can start and stop the event loop like this:
ioloop = IOLoop()
async_method(ioloop=ioloop, callback=ioloop.stop)
ioloop.start()
ioloop.start() will return after async_method has run its callback,
whether that callback was invoked before or after ioloop.start.
Cependant, je ne sais pas comment intégrer cela dans mon programme. J'ai en fait une classe qui encapsule le serveur Web (ayant ses propres fonctions start
et stop
), mais dès que j'appelle le début, le programme (ou les tests) va bien sûr bloquer.
J'ai essayé de démarrer le serveur Web dans un autre processus (en utilisant le package multiprocessing
). C'est la classe qui encapsule le serveur Web:
class Server:
def __init__(self, port=8888):
self.application = tornado.web.Application([ (r"/", Handler) ])
def server_thread(application, port):
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(port)
tornado.ioloop.IOLoop.instance().start()
self.process = Process(target=server_thread,
args=(self.application, port,))
def start(self):
self.process.start()
def stop(self):
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
Cependant, stop ne semble pas complètement arrêter le serveur Web car il est toujours en cours d'exécution lors du prochain test, même avec cette configuration de test:
def setup_method(self, _function):
self.server = Server()
self.server.start()
time.sleep(0.5) # Wait for web server to start
def teardown_method(self, _function):
self.kstore.stop()
time.sleep(0.5)
Comment puis-je démarrer et arrêter un serveur Web Tornado à partir d'un programme Python?
Je viens juste de rencontrer ceci et j'ai trouvé ce problème moi-même, et en utilisant les informations de ce fil, nous avons trouvé ce qui suit. J'ai simplement pris mon code Tornado autonome (copié de tous les exemples) et déplacé le code de départ dans une fonction. J'ai ensuite appelé la fonction en tant que thread. Mon cas est différent car l'appel de thread a été effectué à partir de mon code existant où je viens d'importer les routines startTornado et stopTornado.
La suggestion ci-dessus a semblé bien fonctionner, alors je me suis dit que je fournirais l'exemple de code manquant. J'ai testé ce code sous Linux sur un système FC16 (et corrigé mon type-o initial).
import tornado.ioloop, tornado.web
class Handler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([ (r"/", Handler) ])
def startTornado():
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
def stopTornado():
tornado.ioloop.IOLoop.instance().stop()
if __== "__main__":
import time, threading
threading.Thread(target=startTornado).start()
print "Your web server will self destruct in 2 minutes"
time.sleep(120)
stopTornado()
J'espère que cela aide la prochaine personne.
Voici la solution pour arrêter Torando à partir d'un autre thread. Schildmeijer a fourni un bon indice, mais il m'a fallu un certain temps pour comprendre le dernier exemple qui fonctionne.
S'il vous plaît voir ci-dessous:
import threading
import tornado.ioloop
import tornado.web
import time
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world!\n")
def start_tornado(*args, **kwargs):
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
print "Starting Torando"
tornado.ioloop.IOLoop.instance().start()
print "Tornado finished"
def stop_tornado():
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
print "Asked Tornado to exit"
def main():
t = threading.Thread(target=start_tornado)
t.start()
time.sleep(5)
stop_tornado()
t.join()
if __== "__main__":
main()
Si vous ne voulez pas vous soucier des threads, vous pouvez intercepter un signal d'interruption du clavier:
try:
tornado.ioloop.IOLoop.instance().start()
# signal : CTRL + BREAK on windows or CTRL + C on linux
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
Pour arrêter l'ioloop entier, vous devez simplement appeler la méthode ioloop.stop lorsque vous avez terminé le test unitaire. (N'oubliez pas que la seule méthode thread-safe (documentée) est ioloop.add_callback, c'est-à-dire que si les tests unitaires sont exécutés par un autre thread, vous pouvez envelopper l'appel stop dans un rappel).
S'il suffit d'arrêter le serveur Web http, vous appelez la méthode httpserver . Stop ()
Il y a un problème, avec la solution de Zaar Hai, à savoir qu'elle laisse la prise ouverte. Si je cherchais une solution pour arrêter Tornado, c’est que j’effectuais des tests unitaires sur mon serveur d’applications et j’avais besoin d’un moyen de démarrer/arrêter le serveur entre les tests pour avoir un état vide (session vide, etc.). En laissant le socket ouvert, le second test a toujours rencontré une erreur Address already in use
. Alors je suis venu avec ce qui suit:
import logging as log
from time import sleep
from threading import Thread
import tornado
from tornado.httpserver import HTTPServer
server = None
thread = None
def start_app():
def start():
global server
server = HTTPServer(create_app())
server.listen(TEST_PORT, TEST_Host)
tornado.ioloop.IOLoop.instance().start()
global thread
thread = Thread(target=start)
thread.start()
# wait for the server to fully initialize
sleep(0.5)
def stop_app():
server.stop()
# silence StreamClosedError Tornado is throwing after it is stopped
log.getLogger().setLevel(log.FATAL)
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
thread.join()
L’idée principale ici est donc de conserver une référence à l’instance HTTPServer
et d’appeler sa méthode stop()
. Et create_app()
renvoie simplement une instance Application
configurée avec des gestionnaires. Maintenant, vous pouvez utiliser ces méthodes dans vos tests unitaires comme ceci:
class FoobarTest(unittest.TestCase):
def setUp(self):
start_app()
def tearDown(self):
stop_app()
def test_foobar(self):
# here the server is up and running, so you can make requests to it
pass
Si vous avez besoin de ce comportement pour les tests unitaires, consultez tornado.testing.AsyncTestCase .
Par défaut, un nouvel IOLoop est construit pour chaque test et est disponible sous la forme self.io_loop. Cet IOLoop doit être utilisé dans la construction de clients/serveurs HTTP, etc. Si le code testé requiert un IOLoop global, les sous-classes doivent remplacer get_new_ioloop pour le renvoyer.
Si vous avez besoin de démarrer et d'arrêter un IOLoop pour une autre raison et que vous ne pouvez pas appeler ioloop.stop () depuis un rappel pour une raison quelconque, une implémentation multithread est possible. Toutefois, pour éviter les conditions de concurrence, vous devez synchroniser l'accès à l'ioloop, ce qui est en fait impossible. Quelque chose comme ce qui suit entraînera une impasse:
Fil 1:
with lock:
ioloop.start()
Fil 2:
with lock:
ioloop.stop()
parce que le thread 1 ne relâche jamais le verrou (start () bloque) et que le thread 2 attend que le verrou soit relâché pour arrêter l'ioloop.
La seule façon de le faire est que le thread 2 appelle ioloop.add_callback (ioloop.stop), qui appellera stop () sur le thread 1 lors de la prochaine itération de la boucle d'événement. Rassurez-vous, ioloop.add_callback () est thread-safe .
IOloop.instance () de Tornado a du mal à s’arrêter à partir d’un signal externe lorsqu’il est exécuté sous multi-traitement.Process.
La seule solution que je trouve qui fonctionne de manière cohérente , consiste à utiliser Process.terminate ():
import tornado.ioloop, tornado.web
import time
import multiprocessing
class Handler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([ (r"/", Handler) ])
class TornadoStop(Exception):
pass
def stop():
raise TornadoStop
class worker(multiprocessing.Process):
def __init__(self):
multiprocessing.Process.__init__(self)
application.listen(8888)
self.ioloop = tornado.ioloop.IOLoop.instance()
def run(self):
self.ioloop.start()
def stop(self, timeout = 0):
self.ioloop.stop()
time.sleep(timeout)
self.terminate()
if __== "__main__":
w = worker()
print 'starting server'
w.start()
t = 2
print 'waiting {} seconds before stopping'.format(t)
for i in range(t):
time.sleep(1)
print i
print 'stopping'
w.stop(1)
print 'stopped'
Ajoutez simplement ceci avant le début ():
IOLoop.instance (). Add_timeout (10, IOLoop.instance (). Stop)
Il enregistre la fonction d'arrêt en tant que rappel dans la boucle et l'exécute 10 secondes après le début.
Nous souhaitons utiliser un multiprocessing.Process
avec un tornado.ioloop.IOLoop
pour contourner le cPython GIL en termes de performances et d’indépendance. Pour avoir accès à IOLoop, nous devons utiliser Queue
pour faire passer le signal d'arrêt.
Voici un exemple minimaliste:
class Server(BokehServer)
def start(self, signal=None):
logger.info('Starting server on http://localhost:%d'
% (self.port))
if signal is not None:
def shutdown():
if not signal.empty():
self.stop()
tornado.ioloop.PeriodicCallback(shutdown, 1000).start()
BokehServer.start(self)
self.ioloop.start()
def stop(self, *args, **kwargs): # args important for signals
logger.info('Stopping server...')
BokehServer.stop(self)
self.ioloop.stop()
Le processus
import multiprocessing as mp
import signal
from server import Server # noqa
class ServerProcess(mp.Process):
def __init__(self, *args, **kwargs):
self.server = Server(*args, **kwargs)
self.shutdown_signal = _mp.Queue(1)
mp.Process.__init__(self)
signal.signal(signal.SIGTERM, self.server.stop)
signal.signal(signal.SIGINT, self.server.stop)
def run(self):
self.server.start(signal=self.shutdown_signal)
def stop(self):
self.shutdown_signal.put(True)
if __== '__main__':
p = ServerProcess()
p.start()
À votre santé!