aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/bot.py16
-rw-r--r--bot/exts/info/reddit.py2
-rw-r--r--bot/exts/moderation/infraction/_scheduler.py38
-rw-r--r--bot/exts/moderation/watchchannels/_watchchannel.py17
4 files changed, 59 insertions, 14 deletions
diff --git a/bot/bot.py b/bot/bot.py
index 36cf7d30a..cdb4e72a9 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
@@ -48,6 +49,9 @@ class Bot(commands.Bot):
self.stats = AsyncStatsClient(self.loop, statsd_url, 8125, prefix="bot")
+ # 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')
@@ -170,6 +174,16 @@ 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.
+ with suppress(Exception):
+ [self.unload_extension(ext) for ext in tuple(self.extensions)]
+ [self.remove_cog(cog) for cog in tuple(self.cogs)]
+
+ # 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()
await self.api_client.close()
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 bebade0ae..6efa5b1e0 100644
--- a/bot/exts/moderation/infraction/_scheduler.py
+++ b/bot/exts/moderation/infraction/_scheduler.py
@@ -74,8 +74,23 @@ 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.NotFound:
+ # When user joined and then right after this left again before action completed, this can't add roles
+ log.info(f"Can't reapply {infraction['type']} to user {infraction['user']} because user left again.")
+ except discord.HTTPException as e:
+ if e.code == 10007:
+ log.info(f"Can't reapply {infraction['type']} to user {infraction['user']} because user left again.")
+ else:
+ log.warning(
+ (
+ 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,
@@ -155,6 +170,8 @@ class InfractionScheduler:
if expiry:
# Schedule the expiration of the infraction.
self.schedule_expiration(infraction)
+ except discord.NotFound:
+ log.info(f"Can't apply {infraction['type']} to user {infraction['user']} because user left from guild.")
except discord.HTTPException as e:
# Accordingly display that applying the infraction failed.
# Don't use ctx.message.author; antispam only patches ctx.author.
@@ -166,6 +183,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:
+ log.info(
+ f"Can't apply {infraction['type']} to user {infraction['user']} because user left from guild."
+ )
else:
log.exception(log_msg)
failed = True
@@ -337,10 +358,17 @@ class InfractionScheduler:
log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions.")
log_text["Failure"] = "The bot lacks permissions to do this (role hierarchy?)"
log_content = mod_role.mention
+ except discord.NotFound:
+ log.info(f"Can't pardon {infraction['type']} for user {infraction['user']} because user left from guild.")
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:
+ log.info(
+ f"Can't pardon {infraction['type']} for user {infraction['user']} because user left from guild."
+ )
+ 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..8894762f3 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.error(
+ 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
- )