Skip to content

Commit faf5cc7

Browse files
committed
invlidate cluster info cache after any error in get/set/delete methods
1 parent 5319cdf commit faf5cc7

File tree

4 files changed

+89
-14
lines changed

4 files changed

+89
-14
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ Testing
127127

128128
Run the tests like this::
129129

130-
nosetest
130+
nosetests

django_elasticache/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
VERSION = (0, 0, 3)
1+
VERSION = (0, 0, 4)
22
__version__ = '.'.join(map(str, VERSION))

django_elasticache/memcached.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@
22
Backend for django cache
33
"""
44
import socket
5+
from functools import wraps
56
from django.core.cache import InvalidCacheBackendError
67
from django.core.cache.backends.memcached import PyLibMCCache
7-
from django.utils.functional import cached_property
88
from .cluster_utils import get_cluster_info
99

1010

11+
def invalidate_cache_after_error(f):
12+
"""
13+
catch any exception and invalidate internal cache with list of nodes
14+
"""
15+
@wraps(f)
16+
def wrapper(self, *args, **kwds):
17+
try:
18+
return f(self, *args, **kwds)
19+
except Exception:
20+
self.clear_cluster_nodes_cache()
21+
raise
22+
return wrapper
23+
24+
1125
class ElastiCache(PyLibMCCache):
1226
"""
1327
backend for Amazon ElastiCache (memcached) with auto discovery mode
@@ -40,18 +54,25 @@ def update_params(self, params):
4054
'ketama': True
4155
}
4256

43-
@cached_property
57+
def clear_cluster_nodes_cache(self):
58+
"""clear internal cache with list of nodes in cluster"""
59+
if hasattr(self, '_cluster_nodes_cache'):
60+
del self._cluster_nodes_cache
61+
4462
def get_cluster_nodes(self):
4563
"""
4664
return list with all nodes in cluster
4765
"""
48-
server, port = self._servers[0].split(':')
49-
try:
50-
return get_cluster_info(server, port)['nodes']
51-
except (socket.gaierror, socket.timeout) as err:
52-
raise Exception('Cannot connect to cluster {} ({})'.format(
53-
self._servers[0], err
54-
))
66+
if not hasattr(self, '_cluster_nodes_cache'):
67+
server, port = self._servers[0].split(':')
68+
try:
69+
self._cluster_nodes_cache = (
70+
get_cluster_info(server, port)['nodes'])
71+
except (socket.gaierror, socket.timeout) as err:
72+
raise Exception('Cannot connect to cluster {} ({})'.format(
73+
self._servers[0], err
74+
))
75+
return self._cluster_nodes_cache
5576

5677
@property
5778
def _cache(self):
@@ -67,10 +88,30 @@ def _cache(self):
6788
if client:
6889
return client
6990

70-
client = self._lib.Client(self.get_cluster_nodes)
91+
client = self._lib.Client(self.get_cluster_nodes())
7192
if self._options:
7293
client.behaviors = self._options
7394

7495
container._client = client
7596

7697
return client
98+
99+
@invalidate_cache_after_error
100+
def get(self, *args, **kwargs):
101+
return super(ElastiCache, self).get(*args, **kwargs)
102+
103+
@invalidate_cache_after_error
104+
def get_many(self, *args, **kwargs):
105+
return super(ElastiCache, self).get_many(*args, **kwargs)
106+
107+
@invalidate_cache_after_error
108+
def set(self, *args, **kwargs):
109+
return super(ElastiCache, self).set(*args, **kwargs)
110+
111+
@invalidate_cache_after_error
112+
def set_many(self, *args, **kwargs):
113+
return super(ElastiCache, self).set_many(*args, **kwargs)
114+
115+
@invalidate_cache_after_error
116+
def delete(self, *args, **kwargs):
117+
return super(ElastiCache, self).delete(*args, **kwargs)

tests/test_backend.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,48 @@ def test_split_servers(get_cluster_info):
5353

5454
@patch('django.conf.settings', global_settings)
5555
@patch('django_elasticache.memcached.get_cluster_info')
56-
def test_property_cache(get_cluster_info):
56+
def test_node_info_cache(get_cluster_info):
5757
from django_elasticache.memcached import ElastiCache
58-
backend = ElastiCache('h:0', {})
5958
servers = ['h1:p', 'h2:p']
6059
get_cluster_info.return_value = {
6160
'nodes': servers
6261
}
62+
63+
backend = ElastiCache('h:0', {})
6364
backend._lib.Client = Mock()
6465
backend.set('key1', 'val')
66+
backend.get('key1')
6567
backend.set('key2', 'val')
68+
backend.get('key2')
6669
backend._lib.Client.assert_called_once_with(servers)
70+
eq_(backend._cache.get.call_count, 2)
71+
eq_(backend._cache.set.call_count, 2)
72+
73+
get_cluster_info.assert_called_once_with('h', '0')
74+
75+
76+
@patch('django.conf.settings', global_settings)
77+
@patch('django_elasticache.memcached.get_cluster_info')
78+
def test_invalidate_cache(get_cluster_info):
79+
from django_elasticache.memcached import ElastiCache
80+
servers = ['h1:p', 'h2:p']
81+
get_cluster_info.return_value = {
82+
'nodes': servers
83+
}
84+
85+
backend = ElastiCache('h:0', {})
86+
backend._lib.Client = Mock()
87+
assert backend._cache
88+
backend._cache.get = Mock()
89+
backend._cache.get.side_effect = Exception()
90+
try:
91+
backend.get('key1', 'val')
92+
except Exception:
93+
pass
94+
backend._local._client = None
95+
try:
96+
backend.get('key1', 'val')
97+
except Exception:
98+
pass
99+
eq_(backend._cache.get.call_count, 2)
100+
eq_(get_cluster_info.call_count, 2)

0 commit comments

Comments
 (0)