Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call Async function in Signal Handler, Asyncio

I am making a Discord bot using discord.py and sometimes the script I use to run it needs to close out the bot. When I close it out without using signal handlers there is a lot of errors about a loop not closing, so I added a signal handler (using the code below) and inside I need to call client.close() and client.logout(), but the problem is those are async functions and thus require me to await them, but I can't await the functions since the signal handler can't be an async function.

Here is the code:

def handler():
    print("Logging out of Discord Bot")
    client.logout()
    client.close()
    sys.exit()

@client.event
async def on_ready():
    print('We have logged in as {0.user}'.format(client))

    for signame in ('SIGINT', 'SIGTERM'):
        client.loop.add_signal_handler(getattr(signal, signame),
                                lambda: asyncio.ensure_future(handler()))

Is there a way to either logout properly using the signal handler, or at least just silence the warnings and errors from inside the code so no errors are printed in the console.

like image 862
Jared Cohen Avatar asked Apr 07 '26 22:04

Jared Cohen


1 Answers

Your approach is on the right track - since add_signal_handler expects an ordinary function and not an async function, you do need to call ensure_future (or its cousin create_task) to submit an async function to run in the event loop. The next step is to actually make handler async, and await the coroutines it invokes:

async def handler():
    print("Logging out of Discord Bot")
    await client.logout()
    await client.close()
    asyncio.get_event_loop().stop()

Note that I changed sys.exit() to explicit stopping of the event loop, because asyncio doesn't react well to sys.exit() being invoked from the middle of a callback (it catches the SystemExit exception and complains of an unretrieved exception).

Since I don't have discord to test, I tested it by changing the logout and close with a sleep:

import asyncio, signal

async def handler():
    print("sleeping a bit...")
    await asyncio.sleep(0.2)
    print('exiting')
    asyncio.get_event_loop().stop()

def setup():
    loop = asyncio.get_event_loop()
    for signame in ('SIGINT', 'SIGTERM'):
        loop.add_signal_handler(getattr(signal, signame),
                                lambda: asyncio.create_task(handler()))

setup()
asyncio.get_event_loop().run_forever()

If you are starting the event loop using something other than run_forever, such as asyncio.run(some_function()), then you will need to replace loop.stop() with the code that sets whatever event the main coroutine awaits. For example, if it awaits server.serve_forever() on a server, then you'd pass the server object to handler and call server.close(), and so on.

like image 135
user4815162342 Avatar answered Apr 17 '26 21:04

user4815162342