Как остановить веб-сервер Tornado?
Я немного поиграл с веб-сервером Tornado и пришел к тому, что хочу остановить веб-сервер (например, во время модульного тестирования). Следующий простой пример существует на веб-странице 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 __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Как только tornado.ioloop.IOLoop.instance().start()
вызывается, он блокирует программу (или текущий поток). Чтение исходного кода для объекта IOLoop
дает этот пример в документации для функции 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.
Однако я не знаю, как интегрировать это в мою программу. На самом деле у меня есть класс, который инкапсулирует веб-сервер (имея собственные функции start
и stop
), но как только я начну звонить, программа (или тесты), конечно же, заблокирует.
Я попытался запустить веб-сервер в другом процессе (используя пакет multiprocessing
). Это класс, который обертывает веб-сервер:
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)
Однако остановка, похоже, не полностью останавливает веб-сервер, поскольку она все еще работает в следующем тесте, даже с этой тестовой настройкой:
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)
Как я могу запустить и остановить веб-сервер Tornado из программы Python?
Ответы
Ответ 1
Я просто столкнулся с этим и сам нашел эту проблему, и с помощью информации из этой темы вышло следующее. Я просто взял мой рабочий код "Торнадо" (скопировал из всех примеров) и перевел фактический начальный код в функцию. Затем я назвал эту функцию потоком потока. Мой случай отличается тем, что вызов по потоку был выполнен из моего существующего кода, где я только что импортировал процедуры startTornado и stopTornado.
Вышеприведенное предложение, похоже, отлично работает, поэтому я решил, что поставлю отсутствующий примерный код. Я тестировал этот код под Linux в системе FC16 (и исправил свой начальный тип-o).
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 __name__ == "__main__":
import time, threading
threading.Thread(target=startTornado).start()
print "Your web server will self destruct in 2 minutes"
time.sleep(120)
stopTornado()
Надеюсь, это поможет следующему человеку.
Ответ 2
Вот решение, как остановить Torando из другого потока. Schildmeijer дал хороший намек, но мне потребовалось некоторое время, чтобы на самом деле изобразить последний пример, который работает.
См. ниже:
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 __name__ == "__main__":
main()
Ответ 3
Если вы не хотите беспокоиться о потоках, вы можете поймать сигнал прерывания клавиатуры:
try:
tornado.ioloop.IOLoop.instance().start()
# signal : CTRL + BREAK on windows or CTRL + C on linux
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
Ответ 4
Чтобы остановить весь ioloop, вы просто вызываете метод ioloop.stop, когда закончите unit test. (Помните, что единственным (документированным) потокобезопасным методом является ioloop.add_callback, т.е. Если модульные тесты выполняются другим потоком, вы можете обернуть вызов остановки в обратном вызове)
Если его достаточно, чтобы остановить веб-сервер http, вы вызываете httpserver.stop() method
Ответ 5
Если вам нужно это поведение для модульного тестирования, посмотрите tornado.testing.AsyncTestCase.
По умолчанию для каждого теста создается новый IOLoop и доступен как self.io_loop. Этот IOLoop следует использовать при построении HTTP-клиентов/серверов и т.д. Если для тестируемого кода требуется глобальный IOLoop, подклассы должны переопределять get_new_ioloop для его возврата.
Если вам нужно запустить и остановить IOLoop для какой-либо другой цели, и по какой-либо причине невозможно вызвать вызов ioloop.stop() из обратного вызова, возможна многопоточная реализация. Однако, чтобы избежать условий гонки, вам необходимо синхронизировать доступ к ioloop, что на самом деле невозможно. Что-то вроде следующего приведет к тупику:
Тема 1:
with lock:
ioloop.start()
Тема 2:
with lock:
ioloop.stop()
потому что поток 1 никогда не освободит блокировку (start() блокирует), и поток 2 будет ждать, пока блокировка не будет отпущена, чтобы остановить ioloop.
Единственный способ сделать это, чтобы поток 2 вызывал ioloop.add_callback (ioloop.stop), который вызовет stop() в потоке 1 в цикле событий следующей итерации. Будьте уверены, ioloop.add_callback() поточно-безопасный.
Ответ 6
Tornado IOloop.instance() имеет проблемы с остановкой внешнего сигнала при запуске под многопроцессорным процессом.
Единственное решение, с которым я столкнулся, работает последовательно, используя 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 __name__ == "__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'
Ответ 7
Просто добавьте это перед началом():
IOLoop.instance(). Add_timeout (10, IOLoop.instance(). Остановить)
Он зарегистрирует функцию останова как обратный вызов в цикле и сделает это через 10 секунд после начала
Ответ 8
Существует проблема с решением Zaar Hai, а именно, что она не открыта. Причина, по которой я искал решение, чтобы остановить Tornado, - это запуск модульных тестов против моего сервера приложений, и мне нужен был способ запустить/остановить сервер между тестами, чтобы иметь ясное состояние (пустой сеанс и т.д.). Если оставить сокет открытым, второй тест всегда сталкивался с ошибкой Address already in use
. Поэтому я придумал следующее:
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()
Итак, основная идея здесь - сохранить ссылку на экземпляр HTTPServer
и вызвать его метод stop()
. И create_app()
просто возвращает экземпляр Application
, настроенный с помощью обработчиков. Теперь вы можете использовать эти методы в своих модульных тестах следующим образом:
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
Ответ 9
Мы хотим использовать multiprocessing.Process
с tornado.ioloop.IOLoop
для работы с cPython GIL для производительности и независимости. Чтобы получить доступ к IOLoop, нам нужно использовать Queue
для передачи сигнала выключения через.
Вот минималистский пример:
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()
Процесс
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 __name__ == '__main__':
p = ServerProcess()
p.start()
Ура!