-
Notifications
You must be signed in to change notification settings - Fork 25
/
instance_cache.py
172 lines (135 loc) · 4.99 KB
/
instance_cache.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
Based on cachepy.py by Juan Pablo Guereca with additional modifications
for thread safety and simplified to reduce time spent in critical areas.
Module which implements a per GAE instance data cache, similar to what
you can achieve with APC in PHP instances.
Each GAE instance caches the global scope, keeping the state of every
variable on the global scope.
You can go farther and cache other things, creating a caching layer
for each GAE instance, and it's really fast because there is no
network transfer like in memcache. Moreover GAE doesn't charge for
using it and it can save you many memcache and db requests.
Not everything are upsides. You can not use it on every case because:
- There's no way to know if you have set or deleted a key in all the
GAE instances that your app is using. Everything you do with Cachepy
happens in the instance of the current request and you have N
instances, be aware of that.
- The only way to be sure you have flushed all the GAE instances
caches is doing a code upload, no code change required.
- The memory available depends on each GAE instance and your app. I've
been able to set a 60 millions characters string which is like 57 MB
at least. You can cache somethings but not everything.
"""
# TODO(chris): implement an LRU cache. currently we store all sorts of
# things in instance memory by default via layer_cache, and these
# things might never be reaped.
import time
import logging
import os
try:
import threading
except ImportError:
import dummy_threading as threading
_CACHE = {}
_CACHE_LOCK = threading.RLock()
""" Flag to deactivate it on local environment. """
ACTIVE = (not os.environ.get('SERVER_SOFTWARE').startswith('Devel') or
os.environ.get('FAKE_PROD_APPSERVER'))
"""
None means forever.
Value in seconds.
"""
DEFAULT_CACHING_TIME = None
# TODO(csilvers): change the API to be consistent with the memcache API.
def get(key):
""" Gets the data associated to the key or a None """
if ACTIVE is False:
return None
with _CACHE_LOCK:
entry = _CACHE.get(key, None)
if entry is None:
return None
value, expiry = entry
if expiry == None:
return value
current_timestamp = time.time()
if current_timestamp < expiry:
return value
else:
del _CACHE[key]
return None
def get_all_with_prefix(prefix):
""" Return a map of key->data for all keys starting with prefix """
if ACTIVE is False:
return {}
retval = {}
current_timestamp = time.time()
with _CACHE_LOCK:
for key in _CACHE:
if key.startswith(prefix):
value, expiry = _CACHE[key]
if expiry is not None:
if current_timestamp >= expiry:
del _CACHE[key]
continue
retval[key] = value
return retval
def set(key, value, expiry=DEFAULT_CACHING_TIME):
"""
Sets a key in the current instance
key, value, expiry seconds till it expires
"""
if ACTIVE is False:
return None
if expiry != None:
expiry = time.time() + int(expiry)
try:
with _CACHE_LOCK:
_CACHE[key] = (value, expiry)
except MemoryError:
# It doesn't seems to catch the exception, something in the
# GAE's python runtime probably.
logging.info("%s memory error setting key '%s'" % (__name__, key))
def increment(key, expiry=DEFAULT_CACHING_TIME):
"""
Increments key (setting the result to 1 if key isn't present).
Also resets the expiry for this key.
"""
if ACTIVE is False:
return None
if expiry != None:
expiry = time.time() + int(expiry)
try:
with _CACHE_LOCK:
(old_value, _) = _CACHE.get(key, (0, None))
_CACHE[key] = (old_value + 1, expiry)
except TypeError:
logging.error("Cannot increment instance-cache key '%s': value '%s' "
"is not an integer" % (key, old_value))
except MemoryError:
# It doesn't seems to catch the exception, something in the
# GAE's python runtime probably.
logging.info("%s memory error setting key '%s'" % (__name__, key))
def delete(key):
""" Deletes the key stored in the cache of the current instance,
not all the instances. There's no reason to use it except for
debugging when developing (or reclaiming space using a policy other
than time), use expiry when setting a value instead.
"""
with _CACHE_LOCK:
_CACHE.pop(key, None)
def dump():
"""
Returns the cache dictionary with all the data of the current
instance, not all the instances. There's no reason to use it
except for debugging when developing.
"""
return _CACHE
def flush():
"""
Resets the cache of the current instance, not all the instances.
There's no reason to use it except for debugging when developing.
"""
global _CACHE
with _CACHE_LOCK:
_CACHE = {}