aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar MarkKoz <[email protected]>2020-08-22 09:50:25 -0700
committerGravatar MarkKoz <[email protected]>2020-08-22 09:50:25 -0700
commit2544d192fd7403cf92cc568537bb93aa7a859815 (patch)
treefd3d790b1963a6f952df5b1f14395c83a4c2b9fc
parentExplicitly use kwarg with respect_role_hierarchy (diff)
Decorators: replace asyncio.Lock with a custom object
Concerns were raised over possible race conditions due `asyncio.Lock` internally awaiting coroutines. Does a mere `await` suspend the current coroutine, or does it have to actually await something asynchronous, like a future? Avoid answering that question by doing away with the awaits, which aren't necessary but are there as a consequence of using `asyncio.Lock`. Instead, add a custom `LockGuard` object to replace the previous locks.
-rw-r--r--bot/decorators.py6
-rw-r--r--bot/utils/__init__.py3
-rw-r--r--bot/utils/lock.py23
3 files changed, 28 insertions, 4 deletions
diff --git a/bot/decorators.py b/bot/decorators.py
index 0e84cf37e..3418dfd11 100644
--- a/bot/decorators.py
+++ b/bot/decorators.py
@@ -13,7 +13,7 @@ from discord.ext.commands import Cog, Context, check
from bot.constants import Channels, ERROR_REPLIES, RedirectOutput
from bot.errors import LockedResourceError
-from bot.utils import function
+from bot.utils import LockGuard, function
from bot.utils.checks import in_whitelist_check, with_role_check, without_role_check
log = logging.getLogger(__name__)
@@ -144,11 +144,11 @@ def mutually_exclusive(
# Get the lock for the ID. Create a lock if one doesn't exist yet.
locks = __lock_dicts[namespace]
- lock = locks.setdefault(id_, asyncio.Lock())
+ lock = locks.setdefault(id_, LockGuard())
if not lock.locked():
log.debug(f"{name}: resource {namespace!r}:{id_!r} is free; acquiring it...")
- async with lock:
+ with lock:
return await func(*args, **kwargs)
else:
log.info(f"{name}: aborted because resource {namespace!r}:{id_!r} is locked")
diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py
index 5a6e1811b..0dd9605e8 100644
--- a/bot/utils/__init__.py
+++ b/bot/utils/__init__.py
@@ -2,9 +2,10 @@ from abc import ABCMeta
from discord.ext.commands import CogMeta
+from bot.utils.lock import LockGuard
from bot.utils.redis_cache import RedisCache
-__all__ = ['RedisCache', 'CogABCMeta']
+__all__ = ["CogABCMeta", "LockGuard", "RedisCache"]
class CogABCMeta(CogMeta, ABCMeta):
diff --git a/bot/utils/lock.py b/bot/utils/lock.py
new file mode 100644
index 000000000..8f1b738aa
--- /dev/null
+++ b/bot/utils/lock.py
@@ -0,0 +1,23 @@
+class LockGuard:
+ """
+ A context manager which acquires and releases a lock (mutex).
+
+ Raise RuntimeError if trying to acquire a locked lock.
+ """
+
+ def __init__(self):
+ self._locked = False
+
+ def locked(self) -> bool:
+ """Return True if currently locked or False if unlocked."""
+ return self._locked
+
+ def __enter__(self):
+ if self._locked:
+ raise RuntimeError("Cannot acquire a locked lock.")
+
+ self._locked = True
+
+ def __exit__(self, _exc_type, _exc_value, _traceback): # noqa: ANN001
+ self._locked = False
+ return False # Indicate any raised exception shouldn't be suppressed.