Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save rekolae/4522ce70d4d6a4b7a7d6da135e7efdd2 to your computer and use it in GitHub Desktop.

Select an option

Save rekolae/4522ce70d4d6a4b7a7d6da135e7efdd2 to your computer and use it in GitHub Desktop.
The example to how to shutdown tornado web server gracefully...
"""
Initializes and starts a server and listens to requests on a configured port. Performs a graceful shutdown
when a terminate or keyboardInterrupt signal is caught.
"""
# STD lib imports
import sys
import time
import signal
import asyncio
import logging
from functools import partial
# 3rd-party lib imports
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("ip", default="127.0.0.1", help="IP address of server", type=str)
define("port", default=8888, help="Run on the given port", type=int)
MAX_WAIT_SECONDS_BEFORE_SHUTDOWN = 3
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello world!")
def sig_handler(application: tornado.httpserver.HTTPServer, sig: signal, _frame):
"""
Called when an appropriate signal was detected, handles shutting down the running tornado web application.
The function parameter "_frame" is not used for anything, because it contains stack execution info etc
that is not relevant in this case -> ignore it.
:param application: tornado.httpserver.HTTPServer object
:param sig: Signal that is being handled
:param _frame: Current stack frame
"""
# Get current IOLoop
io_loop = tornado.ioloop.IOLoop.instance()
def stop_loop(app: tornado.httpserver.HTTPServer, deadline: float):
"""
Stop the IOLoop after all connections are closed or after deadline was reached (even though there
still might be active connections).
:param app: tornado.httpserver.HTTPServer object
:param deadline: deadline for shutting down the application
"""
now = time.time()
# Check if there are tasks still in queue
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task() and not t.done()]
if now < deadline and len(tasks) > 0:
logging.info("Awaiting %s pending tasks: %s", len(tasks), tasks)
io_loop.add_timeout(now + 1, stop_loop, app, deadline)
return
# Check if there are active connections to the application
pending_connections = len(app._connections)
if now < deadline and pending_connections > 0:
logging.info("Waiting on %s connections to complete", pending_connections)
io_loop.add_timeout(now + 1, stop_loop, app, deadline)
else:
logging.warning("Continuing with %s connections open", pending_connections)
logging.info("Stopping IOLoop")
io_loop.stop()
logging.info("Shutdown complete")
def shutdown():
"""
Start shutdown sequence for the application
"""
logging.info(f"Shutting down within %s seconds", MAX_WAIT_SECONDS_BEFORE_SHUTDOWN)
try:
stop_loop(application, time.time() + MAX_WAIT_SECONDS_BEFORE_SHUTDOWN)
except BaseException as error:
logging.exception("Error while trying to shutdown Tornado")
raise error
logging.warning('Caught signal: %s', signal.Signals(sig).name)
# Add callback to shutdown function
io_loop.add_callback_from_signal(shutdown)
def main() -> int:
"""
Main function that initializes and starts the application
:return: Return code
"""
# Setup logging
logging.basicConfig(level=logging.INFO)
logging.info("Starting server at %s:%s", options.ip, options.port)
# Initialize server
tornado.options.parse_command_line()
application = tornado.web.Application([
(r"/", MainHandler),
])
server = tornado.httpserver.HTTPServer(application)
server.listen(options.port)
# Catch termination (SIGTERM) and keyboard interrupt (SIGINT) signals and pass them to the signal handler
partial_sig_handler = partial(sig_handler, server)
signal.signal(signal.SIGTERM, partial_sig_handler)
signal.signal(signal.SIGINT, partial_sig_handler)
# Start the event loop for the tornado application
tornado.ioloop.IOLoop.current().start()
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment