Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'Restarting' tornado keeps old application/handlers active #2523

Closed
maartenbreddels opened this issue Oct 30, 2018 · 12 comments
Closed

'Restarting' tornado keeps old application/handlers active #2523

maartenbreddels opened this issue Oct 30, 2018 · 12 comments

Comments

@maartenbreddels
Copy link
Contributor

Thanks for the awesome library, have been using it for years now, and I can usually solve issues with all the resources around, but now I'm stuck.

Based on this answer, I expected this snippet:

#! /usr/bin/env python

import tornado.ioloop
import tornado.web
import time
import datetime

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world!\n")

class MainHandler2(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world2!\n")

def start_app(*args, **kwargs):
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    server = application.listen(7888)
    print("Starting app")
    return server

def start_app2(*args, **kwargs):
    application = tornado.web.Application([
        (r"/", MainHandler2),
    ])
    server = application.listen(7888)
    print("Starting app2")
    return server

def stop_tornado():
    ioloop = tornado.ioloop.IOLoop.current()
    ioloop.add_callback(ioloop.stop)
    print("Asked Tornado to exit")

def main():
    server = start_app()
    tornado.ioloop.IOLoop.current().add_timeout(
        datetime.timedelta(seconds=10),
        stop_tornado)
    tornado.ioloop.IOLoop.current().start()
    print("Tornado finished")
    server.stop()

    # Starting over
    start_app2()
    tornado.ioloop.IOLoop.current().start()
main()

To answer 'Hello, world!' for ~10 seconds, and after printing 'Starting app2' to answer 'Hello world2!'. However, it keeps the old handlers active it seems. Is this supported, maybe a bug, and is there a workaround?

I need this because i start a server to make the browser do something, then I close the server, and later on, an external library might start the server again. Since it hosts files from a tempdir, I see 404s (since the files are deleted). I think I've reduced my issue to this example here.

@garenchan
Copy link
Contributor

garenchan commented Nov 1, 2018

I used your code to reproduce the problem.

After the ioloop restart, accessing the server using a browser still returns “hello world!“ and 304 status code. But using curl to access the server will return “hello world2!" and 200 status code.

The following screenshot is from the pycharm console:
image

It takes a while for the server to respond properly to the browser.

@ploxiln
Copy link
Contributor

ploxiln commented Nov 1, 2018

My guess is that the browser re-used the same connection "keep-alive" and that connection on the tornado side retained its reference to the old server instance/objects.

@garenchan
Copy link
Contributor

@ploxiln , your conjecture is absolutely correct. This question has been bothering me for a few days. Thank you for helping me find the answer.

@maartenbreddels
Copy link
Contributor Author

Thanks a lot, I guess there are still connections open that need to be closed, I'll try to see if I can get it to work with this information!

@garenchan
Copy link
Contributor

garenchan commented Nov 2, 2018

@maartenbreddels , it seems that you need to explicitly close all current connections after you stop the http server. Here's an example from the test code:

tornado/tornado/testing.py

Lines 446 to 451 in cc2cf07

def tearDown(self):
self.http_server.stop()
self.io_loop.run_sync(self.http_server.close_all_connections,
timeout=get_async_test_timeout())
self.http_client.close()
super(AsyncHTTPTestCase, self).tearDown()

    def tearDown(self):
        self.http_server.stop()
        self.io_loop.run_sync(self.http_server.close_all_connections,
                              timeout=get_async_test_timeout())
        self.http_client.close()
        super(AsyncHTTPTestCase, self).tearDown()

@maartenbreddels
Copy link
Contributor Author

Great timing @garenchan, felt like pair-programming.
I finally got it to this, after making server global:

def stop_tornado():
    ioloop = tornado.ioloop.IOLoop.current()
    ioloop.add_future(server.close_all_connections(), lambda x: ioloop.stop())
    print("Asked Tornado to exit")

This seems to work nicely, I'll see if that resolves my original issue, if so, I'll close this.
Many thanks again!

@ploxiln
Copy link
Contributor

ploxiln commented Nov 2, 2018

thanks for investigating the details @garenchan , it's an interesting case

@garenchan
Copy link
Contributor

garenchan commented Nov 2, 2018

Generally, you should stop the server first, that is, close all listening sockets. Then close all connections. This can shut down the server as quickly as possible!

If you close the connections first and new connections are established at the same time, close_all_connections may never be completed.

@maartenbreddels
Copy link
Contributor Author

@garenchan again, are we pair programming? :)
Just realized that. For completeness, this is the working example:

#! /usr/bin/env python

import tornado.ioloop
import tornado.web
import time
import datetime

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world!\n")

class MainHandler2(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world2!\n")

def start_app(*args, **kwargs):
    global server
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    server = application.listen(7888)
    print("Starting app")
    return server

def start_app2(*args, **kwargs):
    global server2
    application = tornado.web.Application([
        (r"/", MainHandler2),
    ])
    server2 = application.listen(7888)
    print("Starting app2")
    return server

def stop_tornado():
    server.stop()
    ioloop = tornado.ioloop.IOLoop.current()
    ioloop.add_future(server.close_all_connections(), lambda x: ioloop.stop())
    print("Asked Tornado to exit")

def main():
    server = start_app()
    tornado.ioloop.IOLoop.current().add_timeout(
        datetime.timedelta(seconds=10),
        stop_tornado)
    tornado.ioloop.IOLoop.current().start()
    print("Tornado finished")

    # Starting over
    start_app2()
    tornado.ioloop.IOLoop.current().start()
main()

(PS: don't use globals in real life)

@garenchan
Copy link
Contributor

@maartenbreddels , I think we're doing pair programming! The code you gave is good enough for me.

@maartenbreddels
Copy link
Contributor Author

Thanks, my original issue in jupyter/nbconvert#901 is now fully solved, thanks!

@AvinashPrajapati
Copy link

AvinashPrajapati commented Feb 10, 2024

Great timing @garenchan, felt like pair-programming. I finally got it to this, after making server global:

def stop_tornado():
    ioloop = tornado.ioloop.IOLoop.current()
    ioloop.add_future(server.close_all_connections(), lambda x: ioloop.stop())
    print("Asked Tornado to exit")

This seems to work nicely, I'll see if that resolves my original issue, if so, I'll close this. Many thanks again!

import asyncio
import tornado


PORT = 8888

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, with new changes")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])


async def start_server():
    app = make_app()
    server = app.listen(PORT)
    print(f"\nStarted :: http://localhost:{PORT}\n")
    return server


async def stop_server(server):
    print(f"\nStopped :: http://localhost:{PORT}\n")
    
    await server.close_all_connections()

    # ioloop = tornado.ioloop.IOLoop.current()
    # await ioloop.stop()

    server.stop()

    print("Asked Tornado to exit")


async def main():
    server = await start_server()
    # print(server)
    while True:
        user_input = await asyncio.get_event_loop().run_in_executor(None, input, "Press 'q' to quit, 'r' to manually reload: ")
        if user_input.lower() == 'q':
            await stop_server(server)
            break
        elif user_input.lower() == 'r':
            await stop_server(server)
            await asyncio.sleep(0.5)
            server = await start_server()
        else:
            continue

if __name__ == "__main__":
    asyncio.run(main()) 

I want to ask that how to clear all the cache or old application/handlers in async way in this: async def stop_server

I tried but not works for me.
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants