10

I'm working on a bot using `discord.py. The bot makes/deletes several channels, and connects to a SQLite database. If the bot were to crash, I want it to

  1. Destroy all temporary voice channels it created.
  2. Disconnect from the SQL database.

Here's the shutdown coroutine:

async def shutdown(self):
    print("Shutting down Canvas...")
    for ch in self.active_channels:
        await client.delete_channel(ch)
    self.db.close()

Things I've tried:

# Canv is the interface between the bot and the data we're collecting
atexit.register(canv.shutdown) 
bot.run(TOKEN)

try:
    bot.loop.run_until_complete(bot.start(TOKEN))
except KeyboardInterrupt or InterruptedError:
    bot.loop.run_until_complete(canv.shutdown())
finally:
    bot.loop.close()

from async_generator import asynccontextmanager

@asynccontextmanager
async def cleanup_context_manager():
    try:
        yield
    finally:
        await canv.shutdown()

with cleanup_context_manager():
    bot.run(TOKEN)

None of these run canv.shutdown(), which is an asyncio.coroutine. How do ensure this code gets run on every type of exit?

I used this post for some info, and I think it's the closest to what I want.

sawyermclane
  • 896
  • 11
  • 28
  • You could try `atexit.register(lambda: asyncio.get_event_loop().run_until_complete(canv.shutdown()))`. For that to work, you will need to remove `loop.close()` from the top-level block. (The program is exiting anyway, so you don't gain anything by explicitly closing the loop.) – user4815162342 Dec 06 '18 at 07:46
  • @user4815162342 The event loop is closed by then. `RuntimeError: Event loop is closed`. It seems that by the time `atexit` calls, the object is already deleted. – sawyermclane Dec 06 '18 at 21:52
  • That's why the second sentence of my comment says "For that to work, you will need to remove `loop.close()`...", and goes on to explain why it is ok to do so. – user4815162342 Dec 06 '18 at 21:56
  • I removed the `loop.close()` from the end of my code, and that didn't have any impact. – sawyermclane Dec 06 '18 at 22:17

2 Answers2

7

You can try something like this

import asyncio
import atexit


@atexit.register
def shutdown(self):
    print("Shutting down Canvas...")
    loop = asyncio.get_event_loop()
    loop.run_until_complete(await_delete_channels())
    self.db.close()


async def await_delete_channels(self):
    # # Works but it is better to do it asynchronously
    # for ch in self.active_channels:
    #    await client.delete_channel(ch)
    #
    # Doing delete_channels() asynchronously
    delete_channels = [client.delete_channel(ch) for ch in self.active_channels]
    await asyncio.wait(delete_channels, return_when=asyncio.ALL_COMPLETED)
  • `RuntimeError: Event loop is closed` when ctrl-c is pressed during `asyncio.run(main())` . Solution is to not use `syncio.run()`. – fuzzyTew Jan 06 '21 at 15:50
1

Try:

try:
    bot.loop.run_until_complete(bot.start(TOKEN))
finally:
    bot.loop.run_until_complete(canv.shutdown())
    bot.loop.close()

You want to delete channels and close db on every kind of script shutdown, not only on crash, right?

Otherwise, try:

try:
    bot.loop.run_until_complete(bot.start(TOKEN))
except Exception:
    bot.loop.run_until_complete(canv.shutdown())
    raise
finally:
    bot.loop.close()

Upd:

According to link you provided:

The information of the exception rasied and the exception itself can be retreived with a standard call to sys.exc_info().

Let's try it out:

import sys

try:
    bot.loop.run_until_complete(bot.start(TOKEN))
finally:
    if sys.exc_info() != (None, None, None):
        bot.loop.run_until_complete(canv.shutdown())
    bot.loop.close()
Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • Yeah, I want these things to happen on shutdown regardless of how that shutdown happened. I learned that the `Client` was suppressing `Exceptions`, so I tried to fix that by adding a `raise`, as suggested [here](https://discordpy.readthedocs.io/en/latest/api.html#discord.on_error), but this _still_ doesn't trigger the except/finally clauses. – sawyermclane Dec 06 '18 at 21:22