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

How to gracefully stop server programatically #2950

Closed
bitrut opened this issue Apr 22, 2018 · 11 comments
Closed

How to gracefully stop server programatically #2950

bitrut opened this issue Apr 22, 2018 · 11 comments

Comments

@bitrut
Copy link
Contributor

bitrut commented Apr 22, 2018

Long story short

I have a aiohtto-based service which has the main purpose to just perform a task running in the background. The http is here just to respond with health status of the background task. I don't see any way to programatically stop aiohttp server in case of exception raised by the task. The whole service should stop, because it doesn't make sense without healthy task. I tried something like this (the example is based on the aiohttp docs):

import logging
from aiohttp.web_runner import GracefulExit

log = logging.getLogger(__name__)

async def some_task(app):
    try:
        some_long_running_task()
    except asyncio.CancelledError:
        pass
    except Exception:
        log.exception('Some logs')
        raise GracefulExit()
    finally:
        await cleanup()

Expected behaviour

I'd expect aiohttp to run all the on_cleanup hooks. Or some documentation on how to programatically shutdown the service.

Actual behaviour

on_cleanup hooks are not called.

Steps to reproduce

Your environment

@bitrut bitrut changed the title How to gracefully stop server from the code How to gracefully stop server programatically Apr 23, 2018
@asvetlov
Copy link
Member

https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.AppRunner.cleanup

@bitrut
Copy link
Contributor Author

bitrut commented Apr 24, 2018

FYI, raising GracefulExit did the job. I just had some issues with concurrency and I've been raising this exception too early in my testing code.
Thanks anyway.

@asvetlov
Copy link
Member

FYI GracefulExit is a private API, use it on your own risk.

@TomGoBravo
Copy link

TomGoBravo commented Aug 27, 2019

I'm running an aiohttp server in its own thread for a test. I'd like to end the test cleanly so called

await app.shutdown()
await app.cleanup()

but the server kept going. :-(
My new plan is to use https://docs.python.org/3/library/multiprocessing.html so I can use terminate or close to stop the server. It looks like the cleanest option of the choices nicely listed at https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/

@webknjaz
Copy link
Member

@TomGoBravo FYI you may experience intermittent problems when running asyncio loop in non-main threads.

@conraddd
Copy link

conraddd commented Dec 6, 2019

@TomGoBravo
Same issue here, the application keeps running after i called app.shutdown() and app.cleanup()

Here is the code

import asyncio
from aiohttp import web

app = web.Application()
routes = web.RouteTableDef()

@routes.get("/hello")
async def hello(request):
    return web.Response(text="Hello World")


async def bootup(this_app):
    asyncio.create_task(background())

async def background():
    await asyncio.sleep(5)
    print("Start shutting down")
    await app.shutdown()
    print("Start cleaning up")
    await app.cleanup()

if __name__ == "__main__":
    app.add_routes(routes)
    app.on_startup.append(bootup)
    web.run_app(app, host="0.0.0.0", port=80)
    print("Finished")

output

======== Running on http://0.0.0.0:80 ========
(Press CTRL+C to quit)
Start shutting down
Start cleaning up

@kohtala
Copy link
Contributor

kohtala commented Apr 7, 2021

This seems to be without a solution. I find related issues #5386, #4617, #3638, ...

I think the new API to solve it is in #2746. But the PR is stalled. I did not go studying further.

@kohtala
Copy link
Contributor

kohtala commented Apr 9, 2021

I solved this by using the web.AppRunner and having the main task just wait on a future until the other tasks have decided they have shut down. The runner.cleanup should perform the shutdown. Something along the lines of:

async def process():
    app = web.Application()
    runner = web.AppRunner(app)
    loop = asyncio.get_running_loop()
    running = loop.create_future()

    async def stop(e):
        await runner.cleanup()
        running.set_exception(e)
    ... # app setup
    await runner.setup()
    site = web.TCPSite(runner, *address)
    await site.start()
    await running

def main(config=None):
    asyncio.run(process())

Would be nice to have a web.serve_app you could just await or cancel with as simple interface as that of web.run_app along the lines of asyncio.Server.serve_forever.

@tilsche
Copy link

tilsche commented Sep 25, 2021

I kindly ask for this to be reopened.

It is very frustrating that there only seem to be two ways of causing a graceful shutdown of an aiohttp application as discussed in this issue:

  1. Raising a GracefulExit, having to use a private API
  2. Manually re-implementing web.run_app, probably by also using private APIs

Even then, it seems questionable just how graceful a shutdown by throwing a SystemExit to the loop really is (see #3638 (comment)). So some documentation would really be appreciated.

@mgraczyk
Copy link

In my case the easiest way to do this in most cases is to send a signal like os.kill(os.getpid(), 15). You could modify the signal handler if necessary for more specific behavior.

@nosahama
Copy link

This did the trick for me and also exits gracefully.

import signal

def shutdown():
    signal.raise_signal(signal.SIGTERM)

Call shutdown() where you'd like to perform the interception.

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

9 participants