diff -r 261778de26ff -r 620f9b141567 thirdparty/google_appengine/google/appengine/api/memcache/memcache_stub.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/thirdparty/google_appengine/google/appengine/api/memcache/memcache_stub.py Tue Aug 26 21:49:54 2008 +0000 @@ -0,0 +1,282 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the memcache API, keeping all data in process memory.""" + + + +import logging +import time + +from google.appengine.api import memcache +from google.appengine.api.memcache import memcache_service_pb + +MemcacheSetResponse = memcache_service_pb.MemcacheSetResponse +MemcacheSetRequest = memcache_service_pb.MemcacheSetRequest +MemcacheIncrementRequest = memcache_service_pb.MemcacheIncrementRequest +MemcacheDeleteResponse = memcache_service_pb.MemcacheDeleteResponse + + +class CacheEntry(object): + """An entry in the cache.""" + + def __init__(self, value, expiration, flags, gettime): + """Initializer. + + Args: + value: String containing the data for this entry. + expiration: Number containing the expiration time or offset in seconds + for this entry. + flags: Opaque flags used by the memcache implementation. + gettime: Used for testing. Function that works like time.time(). + """ + assert isinstance(value, basestring) + assert len(value) <= memcache.MAX_VALUE_SIZE + assert isinstance(expiration, (int, long)) + + self._gettime = gettime + self.value = value + self.flags = flags + self.created_time = self._gettime() + self.will_expire = expiration != 0 + self.locked = False + self._SetExpiration(expiration) + + def _SetExpiration(self, expiration): + """Sets the expiration for this entry. + + Args: + expiration: Number containing the expiration time or offset in seconds + for this entry. If expiration is above one month, then it's considered + an absolute time since the UNIX epoch. + """ + if expiration > (86400 * 30): + self.expiration_time = expiration + else: + self.expiration_time = self._gettime() + expiration + + def CheckExpired(self): + """Returns True if this entry has expired; False otherwise.""" + return self.will_expire and self._gettime() >= self.expiration_time + + def ExpireAndLock(self, timeout): + """Marks this entry as deleted and locks it for the expiration time. + + Used to implement memcache's delete timeout behavior. + + Args: + timeout: Parameter originally passed to memcache.delete or + memcache.delete_multi to control deletion timeout. + """ + self.will_expire = True + self.locked = True + self._SetExpiration(timeout) + + def CheckLocked(self): + """Returns True if this entry was deleted but has not yet timed out.""" + return self.locked and not self.CheckExpired() + + +class MemcacheServiceStub(object): + """Python only memcache service stub. + + This stub keeps all data in the local process' memory, not in any + external servers. + """ + + def __init__(self, gettime=time.time): + """Initializer. + + Args: + gettime: time.time()-like function used for testing. + """ + self._gettime = gettime + self._ResetStats() + + self._the_cache = {} + + def _ResetStats(self): + """Resets statistics information.""" + self._hits = 0 + self._misses = 0 + self._byte_hits = 0 + + def MakeSyncCall(self, service, call, request, response): + """The main RPC entry point. + + Args: + service: Must be name as defined by sub class variable SERVICE. + call: A string representing the rpc to make. Must be part of + MemcacheService. + request: A protocol buffer of the type corresponding to 'call'. + response: A protocol buffer of the type corresponding to 'call'. + """ + assert service == 'memcache' + assert request.IsInitialized() + + attr = getattr(self, '_Dynamic_' + call) + attr(request, response) + + def _GetKey(self, key): + """Retrieves a CacheEntry from the cache if it hasn't expired. + + Does not take deletion timeout into account. + + Args: + key: The key to retrieve from the cache. + + Returns: + The corresponding CacheEntry instance, or None if it was not found or + has already expired. + """ + entry = self._the_cache.get(key, None) + if entry is None: + return None + elif entry.CheckExpired(): + del self._the_cache[key] + return None + else: + return entry + + def _Dynamic_Get(self, request, response): + """Implementation of MemcacheService::Get(). + + Args: + request: A MemcacheGetRequest. + response: A MemcacheGetResponse. + """ + keys = set(request.key_list()) + for key in keys: + entry = self._GetKey(key) + if entry is None or entry.CheckLocked(): + self._misses += 1 + continue + self._hits += 1 + self._byte_hits += len(entry.value) + item = response.add_item() + item.set_key(key) + item.set_value(entry.value) + item.set_flags(entry.flags) + + def _Dynamic_Set(self, request, response): + """Implementation of MemcacheService::Set(). + + Args: + request: A MemcacheSetRequest. + response: A MemcacheSetResponse. + """ + for item in request.item_list(): + key = item.key() + set_policy = item.set_policy() + old_entry = self._GetKey(key) + + set_status = MemcacheSetResponse.NOT_STORED + if ((set_policy == MemcacheSetRequest.SET) or + (set_policy == MemcacheSetRequest.ADD and old_entry is None) or + (set_policy == MemcacheSetRequest.REPLACE and old_entry is not None)): + + if (old_entry is None or + set_policy == MemcacheSetRequest.SET + or not old_entry.CheckLocked()): + self._the_cache[key] = CacheEntry(item.value(), + item.expiration_time(), + item.flags(), + gettime=self._gettime) + set_status = MemcacheSetResponse.STORED + + response.add_set_status(set_status) + + def _Dynamic_Delete(self, request, response): + """Implementation of MemcacheService::Delete(). + + Args: + request: A MemcacheDeleteRequest. + response: A MemcacheDeleteResponse. + """ + for item in request.item_list(): + key = item.key() + entry = self._GetKey(key) + + delete_status = MemcacheDeleteResponse.DELETED + if entry is None: + delete_status = MemcacheDeleteResponse.NOT_FOUND + elif item.delete_time == 0: + del self._the_cache[key] + else: + entry.ExpireAndLock(item.delete_time()) + + response.add_delete_status(delete_status) + + def _Dynamic_Increment(self, request, response): + """Implementation of MemcacheService::Increment(). + + Args: + request: A MemcacheIncrementRequest. + response: A MemcacheIncrementResponse. + """ + key = request.key() + entry = self._GetKey(key) + if entry is None: + return + + try: + old_value = long(entry.value) + except ValueError, e: + logging.error('Increment/decrement failed: Could not interpret ' + 'value for key = "%s" as an integer.', key) + return + + delta = request.delta() + if request.direction() == MemcacheIncrementRequest.DECREMENT: + delta = -delta + + new_value = old_value + delta + if not (0 <= new_value < 2**64): + new_value = 0 + + entry.value = str(new_value) + response.set_new_value(new_value) + + def _Dynamic_FlushAll(self, request, response): + """Implementation of MemcacheService::FlushAll(). + + Args: + request: A MemcacheFlushRequest. + response: A MemcacheFlushResponse. + """ + self._the_cache.clear() + self._ResetStats() + + def _Dynamic_Stats(self, request, response): + """Implementation of MemcacheService::Stats(). + + Args: + request: A MemcacheStatsRequest. + response: A MemcacheStatsResponse. + """ + stats = response.mutable_stats() + stats.set_hits(self._hits) + stats.set_misses(self._misses) + stats.set_byte_hits(self._byte_hits) + stats.set_items(len(self._the_cache)) + + total_bytes = 0 + for key, entry in self._the_cache.iteritems(): + total_bytes += len(entry.value) + stats.set_bytes(total_bytes) + + stats.set_oldest_item_age(1800)