aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Numerlor <[email protected]>2020-12-12 02:37:37 +0100
committerGravatar Numerlor <[email protected]>2020-12-15 05:02:48 +0100
commit3cc32ae30a671a31a3f05c2c8a4af44e09095cc8 (patch)
tree815919801383fb395b89ee385cadad2e65f29d8c
parentCreate 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.py17
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)