aio-libs / aiohttp

Asynchronous HTTP client/server framework for asyncio and Python
https://docs.aiohttp.org
Other
15.14k stars 2.02k forks source link

How to gracefully stop server programatically #2950

Closed bitrut closed 6 years ago

bitrut commented 6 years ago

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

asvetlov commented 6 years ago

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

bitrut commented 6 years ago

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 commented 6 years ago

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

TomGoBravo commented 5 years ago

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 commented 5 years ago

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

conraddd commented 4 years ago

@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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 https://github.com/aio-libs/aiohttp/issues/3638#issuecomment-621256659). So some documentation would really be appreciated.

mgraczyk commented 6 months ago

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 commented 2 months ago

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.