Подтвердить что ты не робот

Как остановить веб-сервер 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?

4b9b3361

Ответ 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()

Ура!