Index: chrome/common/extensions/docs/server2/appengine_memcache.py |
diff --git a/chrome/common/extensions/docs/server2/appengine_memcache.py b/chrome/common/extensions/docs/server2/appengine_memcache.py |
index ce01e8b52866880069f1f3a8d419f7e21fe25865..605ba71653d37181a5c801f5a3dbca66e7cd48ac 100644 |
--- a/chrome/common/extensions/docs/server2/appengine_memcache.py |
+++ b/chrome/common/extensions/docs/server2/appengine_memcache.py |
@@ -2,12 +2,44 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
+import time |
+ |
from appengine_wrappers import memcache |
+from future import Future |
MEMCACHE_FILE_SYSTEM_READ = 'MemcacheFileSystem.Get' |
MEMCACHE_FILE_SYSTEM_STAT = 'MemcacheFileSystem.Stat' |
MEMCACHE_GITHUB_STAT = 'MemcacheGithub.Stat' |
MEMCACHE_BRANCH_UTILITY = 'BranchUtility' |
+MEMCACHE_FILE_SYSTEM_CACHE = 'MemcacheFileSystemCache' |
+MEMCACHE_FILE_SYSTEM_CACHE_LISTING = 'MemcacheFileSystemCache.Listing' |
+ |
+class _CacheEntry(object): |
+ def __init__(self, value, expire_time): |
+ self.value = value |
+ self._never_expires = (expire_time == 0) |
+ self._expiry = time.time() + expire_time |
+ |
+ def HasExpired(self): |
+ if self._never_expires: |
+ return False |
+ return time.time() > self._expiry |
+ |
+class _AsyncGetFuture(object): |
+ def __init__(self, cache, time, namespace, rpc=None, mapping=None): |
not at google - send to devlin
2012/08/20 05:27:10
document these parameters?
I had to read the code
cduvall
2012/08/20 21:28:09
Done.
|
+ self._cache = cache |
+ self._time = time |
+ self._namespace = namespace |
+ self._rpc = rpc |
+ self._mapping = mapping |
+ |
+ def Get(self): |
+ if self._rpc is not None: |
+ result = self._rpc.get_result() |
+ self._cache[self._namespace].update( |
+ dict((k, _CacheEntry(v, self._time)) for k, v in result.iteritems())) |
+ self._mapping.update(result) |
not at google - send to devlin
2012/08/20 05:27:10
this only works if initial_mapping is not None, an
cduvall
2012/08/20 21:28:09
Done.
|
+ return self._mapping |
class AppEngineMemcache(object): |
not at google - send to devlin
2012/08/20 05:27:10
This all just got complicated.
You're gonna hate
cduvall
2012/08/20 21:28:09
Done.
|
"""A wrapper around the standard App Engine memcache that enforces namespace |
@@ -16,15 +48,71 @@ class AppEngineMemcache(object): |
""" |
def __init__(self, branch): |
self._branch = branch |
+ self._cache = {} |
+ |
+ def _SetInMemory(self, key, value, namespace, time): |
+ if namespace not in self._cache: |
+ self._cache[namespace] = {} |
+ self._cache[namespace][key] = _CacheEntry(value, time) |
+ |
+ def _TryGAE(self, key, namespace, time): |
+ result = memcache.get(key, namespace=namespace) |
+ if result is not None: |
+ self._SetInMemory(key, result, namespace, time) |
+ return result |
+ |
+ def _MakeNamespace(self, namespace): |
+ return self._branch + '.' + namespace |
+ |
+ def Set(self, key, value, namespace, time=300): |
not at google - send to devlin
2012/08/20 05:27:10
define 300 as a constant somewhere?
cduvall
2012/08/20 21:28:09
Done.
|
+ namespace = self._MakeNamespace(namespace) |
+ self._SetInMemory(key, value, namespace, time) |
+ memcache.set(key, value, namespace=namespace, time=time) |
not at google - send to devlin
2012/08/20 05:27:10
this can be an async set, right? We don't care abo
cduvall
2012/08/20 21:28:09
There isn't a set_async function in memcache, only
|
+ |
+ def SetMulti(self, mapping, namespace, time=300): |
+ namespace = self._MakeNamespace(namespace) |
+ for k, v in mapping.iteritems(): |
+ self._SetInMemory(k, v, namespace, time) |
+ # Use a batch set? App Engine kept throwing: |
+ # ValueError: Values may not be more than 1000000 bytes in length |
not at google - send to devlin
2012/08/20 05:27:10
Interesting.
Yeah make this a TODO. If it's an as
cduvall
2012/08/20 21:28:09
Done.
|
+ # for the batch set. |
+ self.Set(k, v, namespace, time=time) |
- def Set(self, key, value, namespace, time=60): |
- return memcache.set(key, |
- value, |
- namespace=self._branch + '.' + namespace, |
- time=time) |
+ def Get(self, key, namespace, time=300): |
+ namespace = self._MakeNamespace(namespace) |
+ if namespace not in self._cache: |
+ return self._TryGAE(key, namespace, time) |
+ cache_entry = self._cache[namespace].get(key, None) |
+ if not cache_entry: |
+ return self._TryGAE(key, namespace, time) |
+ if cache_entry.HasExpired(): |
+ self._cache[namespace].pop(key) |
+ return memcache.get(key, namespace=namespace) |
+ return cache_entry.value |
- def Get(self, key, namespace): |
- return memcache.get(key, namespace=self._branch + '.' + namespace) |
+ def GetMulti(self, keys, namespace, time=300): |
+ namespace = self._MakeNamespace(namespace) |
+ keys = keys[:] |
+ mapping = {} |
+ if namespace not in self._cache: |
+ self._cache[namespace] = {} |
+ for key in keys: |
+ cache_entry = self._cache[namespace].get(key, None) |
+ if cache_entry is not None and not cache_entry.HasExpired(): |
not at google - send to devlin
2012/08/20 05:27:10
nit: reverse the condition to make this more reada
cduvall
2012/08/20 21:28:09
Done.
|
+ mapping[key] = cache_entry.value |
+ keys.remove(key) |
+ else: |
+ mapping[key] = None |
+ client = memcache.Client() |
+ rpc = client.get_multi_async(keys, namespace=namespace) |
+ return Future(delegate=_AsyncGetFuture(self._cache, |
+ time, |
+ namespace, |
+ rpc=rpc, |
+ mapping=mapping)) |
def Delete(self, key, namespace): |
- return memcache.delete(key, namespace=self._branch + '.' + namespace) |
+ namespace = self._MakeNamespace(namespace) |
+ if namespace in self._cache: |
+ self._cache[namespace].pop(key) |
+ memcache.delete(key, namespace=namespace) |