Repository: python-discord/bot
Issue: Cancel scheduled tasks when unloading cogs if those tasks will get rescheduled on reload

Sentry Issue: [BOT-7H](https://sentry.io/organizations/python-discord/issues/1813694114/?referrer=github_integration)

This started off as an issue to ignore 404s for deleting offensive messages from our database, but after thinking about it some more, these 404s never should have happened. It turns out there were a duplicate tasks scheduled to delete the same messages. This happened because the filtering cog was reloaded. The cog re-schedules the tasks when it's initialised, but it does so with a new `Scheduler` instance, which means it cannot tell that it's trying to schedule duplicates.

Changed files:
bot/cogs/moderation/scheduler.py, bot/cogs/reminders.py, bot/cogs/filtering.py, bot/cogs/moderation/silence.py

diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py
index 64afd184..4ec95ad7 100644
--- a/bot/cogs/filtering.py
+++ b/bot/cogs/filtering.py
@@ -99,6 +99,10 @@ class Filtering(Cog):
 
         self.bot.loop.create_task(self.reschedule_offensive_msg_deletion())
 
+    def cog_unload(self) -> None:
+        """Cancel scheduled tasks."""
+        self.scheduler.cancel_all()
+
     def _get_filterlist_items(self, list_type: str, *, allowed: bool) -> list:
         """Fetch items from the filter_list_cache."""
         return self.bot.filter_list_cache[f"{list_type.upper()}.{allowed}"].keys()
diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py
index 601e238c..75028d85 100644
--- a/bot/cogs/moderation/scheduler.py
+++ b/bot/cogs/moderation/scheduler.py
@@ -31,6 +31,10 @@ class InfractionScheduler:
 
         self.bot.loop.create_task(self.reschedule_infractions(supported_infractions))
 
+    def cog_unload(self) -> None:
+        """Cancel scheduled tasks."""
+        self.scheduler.cancel_all()
+
     @property
     def mod_log(self) -> ModLog:
         """Get the currently loaded ModLog cog instance."""
diff --git a/bot/cogs/moderation/silence.py b/bot/cogs/moderation/silence.py
index ae4fb7b6..f8a6592b 100644
--- a/bot/cogs/moderation/silence.py
+++ b/bot/cogs/moderation/silence.py
@@ -152,7 +152,8 @@ class Silence(commands.Cog):
         return False
 
     def cog_unload(self) -> None:
-        """Send alert with silenced channels on unload."""
+        """Send alert with silenced channels and cancel scheduled tasks on unload."""
+        self.scheduler.cancel_all()
         if self.muted_channels:
             channels_string = ''.join(channel.mention for channel in self.muted_channels)
             message = f"<@&{Roles.moderators}> channels left silenced on cog unload: {channels_string}"
diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py
index b5998cc0..670493bc 100644
--- a/bot/cogs/reminders.py
+++ b/bot/cogs/reminders.py
@@ -37,6 +37,10 @@ class Reminders(Cog):
 
         self.bot.loop.create_task(self.reschedule_reminders())
 
+    def cog_unload(self) -> None:
+        """Cancel scheduled tasks."""
+        self.scheduler.cancel_all()
+
     async def reschedule_reminders(self) -> None:
         """Get all current reminders from the API and reschedule them."""
         await self.bot.wait_until_guild_available()