Intro
Чтобы правильно решить задачу, давайте ее сначала правильно сформулируем. Итак, мы хотим, чтобы при выполнении
sudo kill -TERM <TORNADO_PROCESS_PID>
(terminate) tornado сервер перестал принимать новые запросы, а уже открытым соединениям вернул ответ и закончил работу. На самом деле, это только один из возможных вариантов в процессе обработки сигналов. Вполне возможно, что вы захотите научить сервер реагировать на другие систмные сигналы, типа HUP, INT, USR1 и т.д. Но давайте разберем эту ситуацию, остальные решаются по аналогии.
Signals
Для обработки сигналов, в python используется модуль signal. Простейший код перехватки сигнала будет выглядить следующим образом:
import signal
def handler(sig, frame):
# you can perform some actions here
print sig
signal.signal(signal.SIGTERM, handler)
Думаю код интуитивно понятен и не нуждается в дополнительных пояснениях.
I/O loop callbacks
Дальше нам нужно учесть два момента. Во-первых, signal может быть получен в любой момент выполнения кода. Единственное, что мы можем сделать из handler-а не беспокоясь за судьбу control flow - навесить callback на i/o loop методом add_callback. Пояснение этого факта можно найти в документации Tornado:
add_callback
... It is safe to call this method from any thread at any time. Note that this is the only method in IOLoop that makes this guarantee; all other interaction with the IOLoop must be done from that IOLoop’s thread. add_callback() may be used to transfer control from other threads to the IOLoop’s thread.
Во-вторых, нужно учитывать, что stop i/o loop приведет к тому, что сервер не отдаст response по тем запросам, которые уже были приняты. Правильный способ "остановки" заключается в том, что в момент получения сигнала мы должны остановить http server (перестанем принимать запросы) + добавить callback на оставновку i/o loop, который будет вызван через некоторое время, например, через пару секунд. Сделать это можно с помощью add_timeout, не забывая что первый параметр - это deadline, а не время ожидания :)
В целом
В результате мы должны получить приблизительно такой код (очень грубо).
import time
import signal
import logging
from tornado.ioloop import IOLoop
from tornado.httpserver import HTTPServer
def sig_handler(sig, frame):
"""Catch signal and init callback"""
logging.warning('Caught signal: %s', sig)
IOLoop.instance().add_callback(shutdown)
def shutdown():
"""Stop server and add callback to stop i/o loop"""
logging.info('Stopping http server')
server.stop()
logging.info('Will shutdown in 2 seconds ...')
io_loop = IOLoop.instance()
io_loop.add_timeout(time.time() + 2, io_loop.stop)
def main():
# You can use more useful approach for this,
# but this is just simple example
global server
# Lets imagine that you already have application and port vars
server = HTTPServer(application)
server.listen(port)
# Init signals handler
signal.signal(signal.SIGTERM, sig_handler)
# This will also catch KeyboardInterrupt exception
signal.signal(signal.SIGINT, sig_handler)
# Starting ...
IOLoop.instance().start()
if __name__ == '__main__':
main()
Комментарии по коду расставлены. Не уверен, что запустится в таком виде. Точнее уверен, что не запустится. Поэтому простое копирование себе в проект не спасет :) К тому же, инициализация и запуск сервера(-ов) в реальном проекте выглядит запутаней и не было смысла вносить эту запутанность в пример, посвященный совершенно другим вещам.
KeyboardInterrupt
KeyboardInterrupt exception является еще одним случаем прерывания работы сервера. Возникает оно в ситуации, когда вы запустили приложение в терминале, а потом нажали ^C. Такую ситуацию можно обрабатывать аналогично, если вспомнить, что ^C приведет к тому, что система отправит процессу INT signal (который python любезно перехватит и выбросит KeyboardInterrupt). Все, что нам нужно здесь сделать - добавить свой обработчик для signal.SIGINT вместо дефолтного.
# ...
# Init signals handler
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
# ...
P.S. Так уж получилось, что на недавнем PyCon я выступил одним из "защитников" Tornado. И это ответ на весьма распространенную претензию о том, что Tornado не имеет режимов graceful stop/restart и вообще не умеет нормально выключатся. В ближайшее время постараюсь выделить время на еще несколько ответов.
⇒ Console Applications with Zend Framework 2.0
⇐ Особенности и примеры скриптов для Custom Keyboard Shortcuts