3

I can't cancel my aiohttp websocket server from within the application. I want to stop the server and shutdown when I get a "cancel" string from the client. Yes, I get it, and I finish my co-routine (websocket_handler), but there are three co-routines from the aiohttp library which still continue working.

enter image description here

Of course, I can invoke asyncio.get_event_loop().stop() at the end of my co-routine, but is there a graceful way for stopping aiohttp server?

From my code one can see that I've tried to use Application().on_shutdown.append(), but it failed.

What is the right way?

#!/usr/bin/env python # -- coding: utf-8 -- import os import asyncio import signal import weakref

import aiohttp.web
from   aiohttp import ClientConnectionError, WSCloseCode

# This restores the default Ctrl+C signal handler, which just kills the process
#https://stackoverflow.com/questions/27480967/why-does-the-asyncios-event-loop-suppress-the-keyboardinterrupt-on-windows
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

HOST = os.getenv('HOST', 'localhost')
PORT = int(os.getenv('PORT', 8881))

async def testhandle(request):
    #Сопрограмма одрабатывающая http-запрос по адресу "http://127.0.0.1:8881/test"
    print("server: into testhandle()")
    return aiohttp.web.Response(text='Test handle')

async def websocket_handler(request):
    #Сопрограмма одрабатывающая ws-запрос по адресу "http://127.0.0.1:8881"   
    print('Websocket connection starting')
    ws = aiohttp.web.WebSocketResponse()
    await ws.prepare(request)
    request.app['websockets'].add(ws)
    print('Websocket connection ready')
    try:
        async for msg in ws:
            if msg.type == aiohttp.WSMsgType.TEXT:
                if msg.data == 'close':
                    print(msg.data) 
                    break    
                else:
                    print(msg.data)
                    await ws.send_str("You said: {}".format(msg.data))
            elif msg.type == aiohttp.WSMsgType.ERROR:
                print('ws connection closed with exception %s' %
                    ws.exception())             
    except (asyncio.CancelledError, ClientConnectionError):   
        pass    # Тут оказываемся когда, клиент отвалился. 
                # В будущем можно тут освобождать ресурсы. 
    finally:
        print('Websocket connection closed')
        request.app['websockets'].discard(ws)
        #pending = asyncio.Task.all_tasks()
        #asyncio.get_event_loop().stop()
    return ws

async def on_shutdown(app):
    for ws in set(app['websockets']):
        await ws.close(code=WSCloseCode.GOING_AWAY, message='Server shutdown')   

def main():
    loop = asyncio.get_event_loop()
    app  = aiohttp.web.Application()
    app['websockets'] = weakref.WeakSet()
    app.on_shutdown.append(on_shutdown)  
    app.add_routes([aiohttp.web.get('/', websocket_handler)])        #, aiohttp.web.get('/test', testhandle)   

    try:
        aiohttp.web.run_app(app, host=HOST, port=PORT, handle_signals=True)
        print("after run_app")
    except Exception as exc:
        print ("in exception")
    finally:
        loop.close()

if __name__ == '__main__':
    main()
karel
  • 5,489
  • 46
  • 45
  • 50

2 Answers2

4

I believe the correct answer is simply:

raise aiohttp.web.GracefulExit()

After catching the exception, it calls all the handlers appended to the on_shutdown and on_cleanup signals and dies.

One can see in the source code the aiohttp.web.run_app() waits for two exceptions: GracefulExit and KeyboardInterrupt. While the latter is rather uninteresting, following the trace of GracefulExit can lead you to this place in web_runner.py, that registers SIGINT and SIGTERM signal handlers to a function with raise GracefulExit().

Indeed, I also managed to gracefully shut it down by raising the signal.SIGINT or signal.SIGTERM from itself, e.g.

import signal
signal.raise_signal(signal.SIGINT)

This was tested to work on Fedora Linux 34, Python 3.9.7, aiohttp 3.7.4.

tlwhitec
  • 1,845
  • 16
  • 15
  • The second option (signal.raise_signal(signal.SIGINT)) is much better because it doesn't print exception messages to the console. Great answer! – tlama Feb 23 '22 at 18:51
  • how can another thread raise the appropriate signal? need to add an endpoint for shutting down, seems unsafe – Erik Aronesty Apr 04 '22 at 17:08
  • @ErikAronesty Signaling from another thread is likely [OK as long as you expect to handle the signal in the main thread](https://docs.python.org/3/library/signal.html#signals-and-threads) (and make sure all things get properly cleaned up after that). You didn't provide much info, but it seems like you could do that in an `on_shutdown` handler (that gets executed in the aiohttp's running_loop thread). Naturally, all threading business is up to you to deal with. – tlwhitec Apr 04 '22 at 21:25
2

https://docs.aiohttp.org/en/v3.0.1/web_reference.html#aiohttp.web.Application.shutdown

app.shutdown()
app.cleanup()

After shutdown you should also do cleanup()

Marat Mkhitaryan
  • 844
  • 2
  • 11
  • 25
  • 3
    The link is no longer correct and app.shutdown() and app.cleanup() don't actually seem to do anything apart from calling the related callbacks. – manu3d Jun 25 '21 at 08:53