diff options
| author | 2020-12-12 02:37:37 +0100 | |
|---|---|---|
| committer | 2020-12-15 05:02:48 +0100 | |
| commit | 3cc32ae30a671a31a3f05c2c8a4af44e09095cc8 (patch) | |
| tree | 815919801383fb395b89ee385cadad2e65f29d8c | |
| parent | Create function for merging function and decorator wrapper globals (diff) | |
Lock inventory refreshes
All commands that refresh the inventories in some way are now locked to
prevent various race conditions that may have occurred in the unlikely
scenario that they got triggered together, the fetching part of the
get command now also has to wait for the running inventory refresh to
finish before proceeding to fetch and parse the html
| -rw-r--r-- | bot/exts/info/doc/_cog.py | 17 |
1 files changed, 14 insertions, 3 deletions
diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py index 933f4500e..11d17222d 100644 --- a/bot/exts/info/doc/_cog.py +++ b/bot/exts/info/doc/_cog.py @@ -17,6 +17,7 @@ from bot.bot import Bot from bot.constants import MODERATION_ROLES, RedirectOutput from bot.converters import InventoryURL, PackageName, ValidURL from bot.pagination import LinePaginator +from bot.utils.lock import lock from bot.utils.messages import wait_for_deletion from bot.utils.scheduling import Scheduler from ._inventory_parser import fetch_inventory @@ -39,6 +40,10 @@ PRIORITY_PACKAGES = ( WHITESPACE_AFTER_NEWLINES_RE = re.compile(r"(?<=\n\n)(\s+)") NOT_FOUND_DELETE_DELAY = RedirectOutput.delete_delay +REFRESH_EVENT = asyncio.Event() +REFRESH_EVENT.set() +COMMAND_LOCK_SINGLETON = "inventory refresh" + doc_cache = DocRedisCache(namespace="Docs") @@ -91,9 +96,6 @@ class CachedParser: If no symbols were fetched from `doc_item`s page before, the HTML has to be fetched before parsing can be queued. """ - if (symbol := self._results.get(doc_item)) is not None: - return symbol - if (symbols_to_queue := self._page_symbols.get(doc_item.url)) is not None: async with bot_instance.http_session.get(doc_item.url) as response: soup = BeautifulSoup(await response.text(encoding="utf8"), "lxml") @@ -176,6 +178,7 @@ class DocCog(commands.Cog): self.bot.loop.create_task(self.init_refresh_inventory()) + @lock("doc", COMMAND_LOCK_SINGLETON, raise_error=True) async def init_refresh_inventory(self) -> None: """Refresh documentation inventory on cog initialization.""" await self.bot.wait_until_guild_available() @@ -258,6 +261,7 @@ class DocCog(commands.Cog): async def refresh_inventory(self) -> None: """Refresh internal documentation inventory.""" + REFRESH_EVENT.clear() log.debug("Refreshing documentation inventory...") for inventory in self.scheduled_inventories: self.inventory_scheduler.cancel(inventory) @@ -279,6 +283,7 @@ class DocCog(commands.Cog): ) for package in await self.bot.api_client.get('bot/documentation-links') ] await asyncio.gather(*coros) + REFRESH_EVENT.set() async def get_symbol_embed(self, symbol: str) -> Optional[discord.Embed]: """ @@ -299,6 +304,9 @@ class DocCog(commands.Cog): markdown = await doc_cache.get(symbol_info) if markdown is None: log.debug(f"Redis cache miss for symbol `{symbol}`.") + if not REFRESH_EVENT.is_set(): + log.debug("Waiting for inventories to be refreshed before processing item.") + await REFRESH_EVENT.wait() markdown = await self.item_fetcher.get_markdown(symbol_info) if markdown is not None: await doc_cache.set(symbol_info, markdown) @@ -374,6 +382,7 @@ class DocCog(commands.Cog): @docs_group.command(name='setdoc', aliases=('s',)) @commands.has_any_role(*MODERATION_ROLES) + @lock("doc", COMMAND_LOCK_SINGLETON, raise_error=True) async def set_command( self, ctx: commands.Context, package_name: PackageName, base_url: ValidURL, inventory_url: InventoryURL @@ -413,6 +422,7 @@ class DocCog(commands.Cog): @docs_group.command(name='deletedoc', aliases=('removedoc', 'rm', 'd')) @commands.has_any_role(*MODERATION_ROLES) + @lock("doc", COMMAND_LOCK_SINGLETON, raise_error=True) async def delete_command(self, ctx: commands.Context, package_name: PackageName) -> None: """ Removes the specified package from the database. @@ -431,6 +441,7 @@ class DocCog(commands.Cog): @docs_group.command(name="refreshdoc", aliases=("rfsh", "r")) @commands.has_any_role(*MODERATION_ROLES) + @lock("doc", COMMAND_LOCK_SINGLETON, raise_error=True) async def refresh_command(self, ctx: commands.Context) -> None: """Refresh inventories and show the difference.""" old_inventories = set(self.base_urls) |