diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/bot.py | 20 | ||||
| -rw-r--r-- | bot/exts/info/reddit.py | 2 | ||||
| -rw-r--r-- | bot/exts/moderation/infraction/_scheduler.py | 34 | ||||
| -rw-r--r-- | bot/exts/moderation/watchchannels/_watchchannel.py | 17 | 
4 files changed, 59 insertions, 14 deletions
| diff --git a/bot/bot.py b/bot/bot.py index 4ebe0a5c3..d5f108575 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -3,7 +3,8 @@ import logging  import socket  import warnings  from collections import defaultdict -from typing import Dict, Optional +from contextlib import suppress +from typing import Dict, List, Optional  import aiohttp  import discord @@ -70,6 +71,9 @@ class Bot(commands.Bot):                  attempt + 1              ) +        # All tasks that need to block closing until finished +        self.closing_tasks: List[asyncio.Task] = [] +      async def cache_filter_list_data(self) -> None:          """Cache all the data in the FilterList on the site."""          full_cache = await self.api_client.get('bot/filter-lists') @@ -145,6 +149,20 @@ class Bot(commands.Bot):      async def close(self) -> None:          """Close the Discord connection and the aiohttp session, connector, statsd client, and resolver.""" +        # Done before super().close() to allow tasks finish before the HTTP session closes. +        for ext in list(self.extensions): +            with suppress(Exception): +                self.unload_extension(ext) + +        for cog in list(self.cogs): +            with suppress(Exception): +                self.remove_cog(cog) + +        # Wait until all tasks that have to be completed before bot is closing is done +        log.trace("Waiting for tasks before closing.") +        await asyncio.gather(*self.closing_tasks) + +        # Now actually do full close of bot          await super().close()          if self.api_client: diff --git a/bot/exts/info/reddit.py b/bot/exts/info/reddit.py index bad4c504d..6790be762 100644 --- a/bot/exts/info/reddit.py +++ b/bot/exts/info/reddit.py @@ -45,7 +45,7 @@ class Reddit(Cog):          """Stop the loop task and revoke the access token when the cog is unloaded."""          self.auto_poster_loop.cancel()          if self.access_token and self.access_token.expires_at > datetime.utcnow(): -            asyncio.create_task(self.revoke_access_token()) +            self.bot.closing_tasks.append(asyncio.create_task(self.revoke_access_token()))      async def init_reddit_ready(self) -> None:          """Sets the reddit webhook when the cog is loaded.""" diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index c062ae7f8..242b2d30f 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -74,8 +74,21 @@ class InfractionScheduler:              return          # Allowing mod log since this is a passive action that should be logged. -        await apply_coro -        log.info(f"Re-applied {infraction['type']} to user {infraction['user']} upon rejoining.") +        try: +            await apply_coro +        except discord.HTTPException as e: +            # When user joined and then right after this left again before action completed, this can't apply roles +            if e.code == 10007 or e.status == 404: +                log.info( +                    f"Can't reapply {infraction['type']} to user {infraction['user']} because user left the guild." +                ) +            else: +                log.exception( +                    f"Got unexpected HTTPException (HTTP {e.status}, Discord code {e.code})" +                    f"when awaiting {infraction['type']} coroutine for {infraction['user']}." +                ) +        else: +            log.info(f"Re-applied {infraction['type']} to user {infraction['user']} upon rejoining.")      async def apply_infraction(          self, @@ -178,6 +191,10 @@ class InfractionScheduler:                  log_msg = f"Failed to apply {' '.join(infr_type.split('_'))} infraction #{id_} to {user}"                  if isinstance(e, discord.Forbidden):                      log.warning(f"{log_msg}: bot lacks permissions.") +                elif e.code == 10007 or e.status == 404: +                    log.info( +                        f"Can't apply {infraction['type']} to user {infraction['user']} because user left from guild." +                    )                  else:                      log.exception(log_msg)                  failed = True @@ -352,9 +369,16 @@ class InfractionScheduler:              log_text["Failure"] = "The bot lacks permissions to do this (role hierarchy?)"              log_content = mod_role.mention          except discord.HTTPException as e: -            log.exception(f"Failed to deactivate infraction #{id_} ({type_})") -            log_text["Failure"] = f"HTTPException with status {e.status} and code {e.code}." -            log_content = mod_role.mention +            if e.code == 10007 or e.status == 404: +                log.info( +                    f"Can't pardon {infraction['type']} for user {infraction['user']} because user left the guild." +                ) +                log_text["Failure"] = "User left the guild." +                log_content = mod_role.mention +            else: +                log.exception(f"Failed to deactivate infraction #{id_} ({type_})") +                log_text["Failure"] = f"HTTPException with status {e.status} and code {e.code}." +                log_content = mod_role.mention          # Check if the user is currently being watched by Big Brother.          try: diff --git a/bot/exts/moderation/watchchannels/_watchchannel.py b/bot/exts/moderation/watchchannels/_watchchannel.py index 7118dee02..f9fc12dc3 100644 --- a/bot/exts/moderation/watchchannels/_watchchannel.py +++ b/bot/exts/moderation/watchchannels/_watchchannel.py @@ -342,11 +342,14 @@ class WatchChannel(metaclass=CogABCMeta):          """Takes care of unloading the cog and canceling the consumption task."""          self.log.trace("Unloading the cog")          if self._consume_task and not self._consume_task.done(): +            def done_callback(task: asyncio.Task) -> None: +                """Send exception when consuming task have been cancelled.""" +                try: +                    task.result() +                except asyncio.CancelledError: +                    self.log.info( +                        f"The consume task of {type(self).__name__} was canceled. Messages may be lost." +                    ) + +            self._consume_task.add_done_callback(done_callback)              self._consume_task.cancel() -            try: -                self._consume_task.result() -            except asyncio.CancelledError as e: -                self.log.exception( -                    "The consume task was canceled. Messages may be lost.", -                    exc_info=e -                ) | 
