Python tornado autoreload not working when configured in a module

Question:

I’m trying to figure out why Python Tornado autoreload feature does not work when configured and started inside a module. The following module contains a MyTornadoApp class that configures and starts autoreload if the proper option is set:

import os
import tornado.autoreload
import tornado.web

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello from index handler!")

class MyTornadoApp:
    """Setup my custom tornado app with default settings"""

    # default tornado settings
    default_settings = dict(
        debug=False,
        autoreload=True,
        compress_response=True,
        port=8080,
    )

    default_handlers = [
        (r"/", IndexHandler),
    ]

    def __init__(self, user_settings):
        self.options = {**self.default_settings, **user_settings}

        if self.options['autoreload'] == True:
            self._autoreload_config()

        self.tornado = tornado.web.Application(self.default_handlers,
                                               **self.options)

    def _autoreload_config(self):
        """Setup autoreload for tornado"""
        print("Setting up autoreload for tornado...")
        # autoreload (exclusive) monitor file
        monitoring_file = os.path.abspath('autoreload')
        if not os.path.isfile(monitoring_file):
            with open(monitoring_file, 'w', encoding='UTF-8') as f:
                f.write('Do NOT delete. Monitoring changes in this file!')
        tornado.autoreload.watch(monitoring_file)
        tornado.autoreload.start()

The main python script below then creates the tornado app but, unfortunately, autoreload only works if start is called (again, according to the Tornado emitted warning message). Any ideias why is this happening? Is it a feature or a bug? What am I missing? TIA.

import asyncio
import tornado.web
from tornadoapp import MyTornadoApp

async def main():
    ################################################################
    #   Why do I need this in order to autoreload work properly???
    ################################################################
    if app.options['autoreload'] == True:
        tornado.autoreload.start()

    app.tornado.listen(app.options['port'])
    print(f"Tornado is listening on port {app.options['port']}")

    await asyncio.Event().wait()

if __name__ == "__main__":
    my_settings = dict(
        debug=False,
        autoreload=True,
    )
    app = MyTornadoApp(my_settings)
    asyncio.run(main())

Please note the warning message: tornado.autoreload started more than once in the same process. But if we remove the tornado.autoreload.start() line from the main() function then autoreload will not be triggered if we change the monitoring file (or any other watched files).

Asked By: bgeneto

||

Answers:

This is an unfortunate quirk/flaw of asyncio.run: it will silently discard any event loop that already existed in the thread and create a new one to run the main function. Since Tornado initializes autoreload when the Application is constructed, that means it uses the wrong event loop when you construct it outside of main.

There are two possible solutions:

  1. Use asyncio.get_event_loop().run_until_complete() instead of asyncio.run(). This should work the way you expect, but this usage is deprecated and will be removed in some future version of python.
  2. Continue to use asyncio.run(), but do everything (including construction of the Application) inside the main function. This is the recommended pattern for the future.
Answered By: Ben Darnell
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.