diff options
author | Bastien Gerard <bastien.gerard@cluepoints.com> | 2021-01-25 09:11:32 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-01-25 11:35:41 -0500 |
commit | ec352d55e947a0ecf61fcc66d31c962bc36df2ad (patch) | |
tree | d8bdd6c0ba4e42326bdf35efe64e8d178a7364e6 | |
parent | 505b35a3b7792a2c3a9d8e06c5f3915d5fe2fbaf (diff) | |
download | dogpile-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.rst | 8 | ||||
-rw-r--r-- | dogpile/cache/api.py | 11 | ||||
-rw-r--r-- | dogpile/cache/backends/memcached.py | 4 | ||||
-rw-r--r-- | dogpile/cache/backends/null.py | 3 | ||||
-rw-r--r-- | dogpile/cache/region.py | 14 | ||||
-rw-r--r-- | dogpile/util/langhelpers.py | 7 | ||||
-rw-r--r-- | tests/cache/_fixtures.py | 23 | ||||
-rw-r--r-- | tests/cache/test_region.py | 5 |
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() |