summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Gerard <bastien.gerard@cluepoints.com>2021-01-25 09:11:32 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2021-01-25 11:35:41 -0500
commitec352d55e947a0ecf61fcc66d31c962bc36df2ad (patch)
treed8bdd6c0ba4e42326bdf35efe64e8d178a7364e6
parent505b35a3b7792a2c3a9d8e06c5f3915d5fe2fbaf (diff)
downloaddogpile-cache-ec352d55e947a0ecf61fcc66d31c962bc36df2ad.tar.gz
Add feature region.key_is_locked()
Added new region method :meth:`.Region.key_is_locked`. Returns True if the given key is subject to the dogpile lock, which would indicate that the generator function is running at that time. Pull request courtesy Bastien Gerard. Fixes: #101 Closes: #198 Pull-request: https://github.com/sqlalchemy/dogpile.cache/pull/198 Pull-request-sha: 8a5ec7aa07dbdfc654617632ffffae6834078263 Change-Id: I8449beeeef0052ac11a4e3d2e8305e10ba70a765
-rw-r--r--docs/build/unreleased/101.rst8
-rw-r--r--dogpile/cache/api.py11
-rw-r--r--dogpile/cache/backends/memcached.py4
-rw-r--r--dogpile/cache/backends/null.py3
-rw-r--r--dogpile/cache/region.py14
-rw-r--r--dogpile/util/langhelpers.py7
-rw-r--r--tests/cache/_fixtures.py23
-rw-r--r--tests/cache/test_region.py5
8 files changed, 75 insertions, 0 deletions
diff --git a/docs/build/unreleased/101.rst b/docs/build/unreleased/101.rst
new file mode 100644
index 0000000..f71b98c
--- /dev/null
+++ b/docs/build/unreleased/101.rst
@@ -0,0 +1,8 @@
+.. change::
+ :tags: feature, region
+ :tickets: 101
+
+ Added new region method :meth:`.CacheRegion.key_is_locked`. Returns True if
+ the given key is subject to the dogpile lock, which would indicate that the
+ generator function is running at that time. Pull request courtesy Bastien
+ Gerard. \ No newline at end of file
diff --git a/dogpile/cache/api.py b/dogpile/cache/api.py
index 3d6bfa4..f54bd68 100644
--- a/dogpile/cache/api.py
+++ b/dogpile/cache/api.py
@@ -85,6 +85,17 @@ class CacheMutex(abc.ABC):
raise NotImplementedError()
+ @abc.abstractmethod
+ def locked(self) -> bool:
+ """Check if the mutex was acquired.
+
+ :return: true if the lock is acquired.
+
+ .. versionadded:: 1.1.2
+
+ """
+ raise NotImplementedError()
+
@classmethod
def __subclasshook__(cls, C):
return hasattr(C, "acquire") and hasattr(C, "release")
diff --git a/dogpile/cache/backends/memcached.py b/dogpile/cache/backends/memcached.py
index 433d986..e1a0184 100644
--- a/dogpile/cache/backends/memcached.py
+++ b/dogpile/cache/backends/memcached.py
@@ -58,6 +58,10 @@ class MemcachedLock(object):
if i < 15:
i += 1
+ def locked(self):
+ client = self.client_fn()
+ return client.get(self.key) is not None
+
def release(self):
client = self.client_fn()
client.delete(self.key)
diff --git a/dogpile/cache/backends/null.py b/dogpile/cache/backends/null.py
index 6ab4cb4..b4ad0fb 100644
--- a/dogpile/cache/backends/null.py
+++ b/dogpile/cache/backends/null.py
@@ -24,6 +24,9 @@ class NullLock(object):
def release(self):
pass
+ def locked(self):
+ return False
+
class NullBackend(CacheBackend):
"""A "null" backend that effectively disables all cache operations.
diff --git a/dogpile/cache/region.py b/dogpile/cache/region.py
index 65ed334..ef0dbc4 100644
--- a/dogpile/cache/region.py
+++ b/dogpile/cache/region.py
@@ -554,6 +554,9 @@ class CacheRegion:
def release(self):
self.lock.release()
+ def locked(self):
+ return self.lock.locked()
+
def _create_mutex(self, key):
mutex = self.backend.get_mutex(key)
if mutex is not None:
@@ -865,6 +868,17 @@ class CacheRegion:
return True
+ def key_is_locked(self, key: KeyType) -> bool:
+ """Return True if a particular cache key is currently being generated
+ within the dogpile lock.
+
+ .. versionadded:: 1.1.2
+
+ """
+ mutex = self._mutex(key)
+ locked: bool = mutex.locked()
+ return locked
+
def get_or_create(
self,
key: KeyType,
diff --git a/dogpile/util/langhelpers.py b/dogpile/util/langhelpers.py
index 2c0588a..a59b24e 100644
--- a/dogpile/util/langhelpers.py
+++ b/dogpile/util/langhelpers.py
@@ -149,3 +149,10 @@ class KeyReentrantMutex:
# the thread ident and unlock.
del self.keys[current_thread]
self.mutex.release()
+
+ def locked(self):
+ current_thread = threading.get_ident()
+ keys = self.keys.get(current_thread)
+ if keys is None:
+ return False
+ return self.key in keys
diff --git a/tests/cache/_fixtures.py b/tests/cache/_fixtures.py
index 1be5a2c..539e27d 100644
--- a/tests/cache/_fixtures.py
+++ b/tests/cache/_fixtures.py
@@ -6,6 +6,7 @@ from threading import Lock
from threading import Thread
import time
from unittest import TestCase
+import uuid
import pytest
@@ -109,6 +110,21 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
backend.delete("some_key")
eq_(backend.get_serialized("some_key"), NO_VALUE)
+ def test_region_is_key_locked(self):
+ reg = self._region()
+ random_key = str(uuid.uuid1())
+ assert not reg.get(random_key)
+ eq_(reg.key_is_locked(random_key), False)
+ # ensures that calling key_is_locked doesn't acquire the lock
+ eq_(reg.key_is_locked(random_key), False)
+
+ mutex = reg.backend.get_mutex(random_key)
+ if mutex:
+ mutex.acquire()
+ eq_(reg.key_is_locked(random_key), True)
+ mutex.release()
+ eq_(reg.key_is_locked(random_key), False)
+
def test_region_set_get_value(self):
reg = self._region()
reg.set("some key", "some value")
@@ -387,11 +403,15 @@ class _GenericMutexTest(_GenericBackendFixture, TestCase):
backend = self._backend()
mutex = backend.get_mutex("foo")
+ assert not mutex.locked()
ac = mutex.acquire()
assert ac
ac2 = mutex.acquire(False)
+ assert mutex.locked()
assert not ac2
mutex.release()
+ assert not mutex.locked()
+
ac3 = mutex.acquire()
assert ac3
mutex.release()
@@ -470,6 +490,9 @@ class MockMutex(object):
def release(self):
return
+ def locked(self):
+ return False
+
class MockBackend(CacheBackend):
def __init__(self, arguments):
diff --git a/tests/cache/test_region.py b/tests/cache/test_region.py
index 467e16a..0a10beb 100644
--- a/tests/cache/test_region.py
+++ b/tests/cache/test_region.py
@@ -202,6 +202,11 @@ class RegionTest(TestCase):
eq_(reg.get_or_create("some key", creator), "some value")
+ def test_key_is_locked(self):
+ reg = self._region()
+
+ eq_(reg.key_is_locked("some key"), False)
+
def test_multi_creator(self):
reg = self._region()