Repository: Rapptz/discord.py

Issue:
cog_unload is not called when an exception is encountered while loading a cog, even if cog_load runs successfully
### Summary

`cog_unload` is not called when an exception occurs while loading a cog. This by itself could be considered 'works as intended', since the cog is assumed to have failed to load, however this behavior occurs even if the `cog_load` method runs successfully, which is an issue, since any resources allocated by the cog here will never get unloaded.

### Reproduction Steps

1. Create a cog with a `cog_unload` method.
2. Optionally add a `cog_load` method to the cog. This isn't required but it helps to demonstrate the issue.
3. Do something that raises an exception during the cog loading process. This can be caused either by raising an exception in the `cog_load` method or by using a decorator with an invalid value (in my case, accidentally using the `discord.ext.commands.command()` decorator with a command name that is already registered elsewhere).
4. Run the bot and attempt to load this cog/extension (in my case via `bot.load_extension`).
5. (See Actual Results for what goes wrong.)

### Minimal Reproducible Code

```python
from discord.ext import commands


async def setup(bot: commands.Bot):
    await bot.add_cog(TestCog(bot))

async def teardown(bot: commands.Bot):
    print("Teardown") # This runs



class TestCog(commands.Cog):
    def __init__(self, bot: commands.Bot):
        print("Initializing cog!") # This runs

    async def cog_load(self):
        print("Loading cog!") # This runs
    
    async def cog_unload(self):
        print("Unloading cog!") # This DOESN'T run!

    @commands.command(name="test")
    async def test1(self, ctx: commands.Context):
        pass
    
    @commands.command(name="test") # This causes the cog to crash while it's loading
    async def test2(self, ctx: commands.Context):
        pass
```


### Expected Results

I would expect at least one (preferably both) of these things to happen:
- The `cog_load` method doesn't run at all under these circumstances.
- The `cog_unload` method should always run if a call to `cog_load` is made by the library.

### Actual Results

This is what happens, step by step, when `bot.load_extension` gets called by the user while attempting to load the cog example above:
1. The `setup` function gets successfully called.
2. The `TestCog` is successfully instantiated ("Initializing cog!" gets printed).
3. The `cog_load` method successfully runs ("Loading cog!" gets printed).
4. The extension will raise a `CommandRegistrationError` because of the duplicate command names. This causes the extension to start unloading.
5. The `cog_unload` method never runs! ("Unloading cog!" never gets printed)
6. The `teardown` function gets successfully called ("Teardown" gets printed).
7. The original `bot.load_extension` raises an exception, with the extension not being loaded, but any resources allocated in `cog_load` are still left open.

### Intents

`discord.Intents.all()`

### System Information

```
- Python v3.9.2-final
- discord.py v2.0.0-alpha
    - discord.py pkg_resources: v2.0.0a4403+g1aaa32d4
- aiohttp v3.7.4.post0
- system info: Linux 5.10.0-9-amd64 #1 SMP Debian 5.10.70-1 (2021-09-30)
```

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Additional Context

Exception generated by the code example given above:
```
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/discord/ext/commands/bot.py", line 929, in _load_from_module_spec
    await setup(self)
  File "/root/bots/test.py", line 5, in setup
    await bot.add_cog(TestCog(bot))
  File "/usr/local/lib/python3.9/dist-packages/discord/ext/commands/bot.py", line 765, in add_cog
    cog = await cog._inject(self, override=override, guild=guild, guilds=guilds)
  File "/usr/local/lib/python3.9/dist-packages/discord/ext/commands/cog.py", line 578, in _inject
    raise e
  File "/usr/local/lib/python3.9/dist-packages/discord/ext/commands/cog.py", line 572, in _inject
    bot.add_command(command)
  File "/usr/local/lib/python3.9/dist-packages/discord/ext/commands/bot.py", line 228, in add_command
    super().add_command(command)
  File "/usr/local/lib/python3.9/dist-packages/discord/ext/commands/core.py", line 1320, in add_command
    raise CommandRegistrationError(command.name)
discord.ext.commands.errors.CommandRegistrationError: The command test is already an existing command or alias.
```


Discussion:

Resolving patch:
diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py
--- a/discord/ext/commands/cog.py
+++ b/discord/ext/commands/cog.py
@@ -616,7 +616,10 @@ async def _inject(self, bot: BotBase, override: bool, guild: Optional[Snowflake]
                     for to_undo in self.__cog_commands__[:index]:
                         if to_undo.parent is None:
                             bot.remove_command(to_undo.name)
-                    raise e
+                    try:
+                        await maybe_coroutine(self.cog_unload)
+                    finally:
+                        raise e
 
         # check if we're overriding the default
         if cls.bot_check is not Cog.bot_check:


Test patch: