better cache-disk, thanks Leonel

This commit is contained in:
mdipierro
2014-09-04 22:27:52 -05:00
parent 9fd827c561
commit 2d9f0fafdc
3 changed files with 665 additions and 638 deletions
+9 -9
View File
@@ -35,15 +35,15 @@ src:
make clean make clean
### clean up baisc apps ### clean up baisc apps
rm -f routes.py rm -f routes.py
rm -f applications/*/sessions/* rm -rf applications/*/sessions/*
rm -f applications/*/errors/* | echo 'too many files' rm -rf applications/*/errors/* | echo 'too many files'
rm -f applications/*/cache/* rm -rf applications/*/cache/*
rm -f applications/admin/databases/* rm -rf applications/admin/databases/*
rm -f applications/welcome/databases/* rm -rf applications/welcome/databases/*
rm -f applications/examples/databases/* rm -rf applications/examples/databases/*
rm -f applications/admin/uploads/* rm -rf applications/admin/uploads/*
rm -f applications/welcome/uploads/* rm -rf applications/welcome/uploads/*
rm -f applications/examples/uploads/* rm -rf applications/examples/uploads/*
### NO MORE make epydoc ### NO MORE make epydoc
# make epydoc # make epydoc
### make welcome layout and appadmin the default ### make welcome layout and appadmin the default
+1 -1
View File
@@ -1 +1 @@
Version 2.9.6-stable+timestamp.2014.09.04.22.16.03 Version 2.9.6-stable+timestamp.2014.09.04.22.27.48
+126 -99
View File
@@ -20,20 +20,27 @@ caching will be provided by the GAE memcache
(see gluon.contrib.gae_memcache) (see gluon.contrib.gae_memcache)
""" """
import time import time
import portalocker import shutil
import shelve
import thread import thread
import os import os
import sys
import logging import logging
import re import re
import hashlib import hashlib
import datetime import datetime
import tempfile
from gluon import recfile
try: try:
from gluon import settings from gluon import settings
have_settings = True have_settings = True
except ImportError: except ImportError:
have_settings = False have_settings = False
try:
import cPickle as pickle
except:
import pickle
logger = logging.getLogger("web2py.cache") logger = logging.getLogger("web2py.cache")
__all__ = ['Cache', 'lazy_cache'] __all__ = ['Cache', 'lazy_cache']
@@ -122,7 +129,7 @@ class CacheAbstract(object):
Auxiliary function called by `clear` to search and clear cache entries Auxiliary function called by `clear` to search and clear cache entries
""" """
r = re.compile(regex) r = re.compile(regex)
for (key, value) in storage.items(): for key in storage:
if r.match(str(key)): if r.match(str(key)):
del storage[key] del storage[key]
break break
@@ -242,7 +249,6 @@ class CacheOnDisk(CacheAbstract):
This is implemented as a shelve object and it is shared by multiple web2py This is implemented as a shelve object and it is shared by multiple web2py
processes (and threads) as long as they share the same filesystem. processes (and threads) as long as they share the same filesystem.
The file is locked when accessed.
Disk cache provides persistance when web2py is started/stopped but it slower Disk cache provides persistance when web2py is started/stopped but it slower
than `CacheInRam` than `CacheInRam`
@@ -250,137 +256,158 @@ class CacheOnDisk(CacheAbstract):
Values stored in disk cache must be pickable. Values stored in disk cache must be pickable.
""" """
def _close_shelve_and_unlock(self): class PersistentStorage(object):
try: """
if self.storage: Implements a key based storage in disk.
self.storage.close() """
except ValueError: def __init__(self, folder):
pass self.folder = folder
finally: # Check the best way to do atomic file replacement.
self.storage = None if sys.version_info >= (3, 3):
if self.locker and self.locked: self.replace = os.replace
portalocker.unlock(self.locker) elif sys.platform == "win32":
self.locker.close() import ctypes
self.locked = False from ctypes import wintypes
ReplaceFile = ctypes.windll.kernel32.ReplaceFileW
ReplaceFile.restype = wintypes.BOOL
ReplaceFile.argtypes = [
wintypes.LPWSTR,
wintypes.LPWSTR,
wintypes.LPWSTR,
wintypes.DWORD,
wintypes.LPVOID,
wintypes.LPVOID,
]
def _open_shelve_and_lock(self): def replace_windows(src, dst):
"""Open and return a shelf object, obtaining an exclusive lock if not ReplaceFile(dst, src, None, 0, 0, 0):
on self.locker first. Replaces the close method of the os.rename(src, dst)
returned shelf instance with one that releases the lock upon
closing."""
storage = None self.replace = replace_windows
locker = None else:
locked = False # POSIX rename() is always atomic
try: self.replace = os.rename
locker = locker = open(self.locker_name, 'a')
portalocker.lock(locker, portalocker.LOCK_EX)
locked = True def __setitem__(self, key, value):
tmp_name, tmp_path = tempfile.mkstemp(dir=self.folder)
tmp = os.fdopen(tmp_name, 'wb')
try: try:
storage = shelve.open(self.shelve_name) pickle.dump((time.time(), value), tmp, pickle.HIGHEST_PROTOCOL)
except: finally:
logger.error('corrupted cache file %s, will try rebuild it' tmp.close()
% self.shelve_name) fullfilename = os.path.join(self.folder, recfile.generate(key))
storage = None if not os.path.exists(os.path.dirname(fullfilename)):
if storage is None: os.makedirs(os.path.dirname(fullfilename))
if os.path.exists(self.shelve_name): self.replace(tmp_path, fullfilename)
os.unlink(self.shelve_name)
storage = shelve.open(self.shelve_name)
if not CacheAbstract.cache_stats_name in storage.keys(): def __getitem__(self, key):
storage[CacheAbstract.cache_stats_name] = { if recfile.exists(key, path=self.folder):
'hit_total': 0, 'misses': 0} timestamp, value = pickle.load(recfile.open(key, 'rb', path=self.folder))
storage.sync() return value
except Exception, e: else:
if storage: raise KeyError
storage.close()
storage = None def __contains__(self, key):
if locked: return recfile.exists(key, path=self.folder)
portalocker.unlock(locker)
locker.close()
locked = False def __delitem__(self, key):
raise RuntimeError( recfile.remove(key, path=self.folder)
'unable to create/re-create cache file %s' % self.shelve_name)
self.locker = locker
self.locked = locked def __iter__(self):
self.storage = storage for dirpath, dirnames, filenames in os.walk(self.folder):
return storage for filename in filenames:
yield filename
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def clear(self):
for key in self:
del self[key]
def __init__(self, request=None, folder=None): def __init__(self, request=None, folder=None):
self.initialized = False self.initialized = False
self.request = request self.request = request
self.folder = folder self.folder = folder
self.storage = {} self.storage = None
def initialize(self): def initialize(self):
if self.initialized: if self.initialized:
return return
else: else:
self.initialized = True self.initialized = True
folder = self.folder folder = self.folder
request = self.request request = self.request
# Lets test if the cache folder exists, if not # Lets test if the cache folder exists, if not
# we are going to create it # we are going to create it
folder = folder or os.path.join(request.folder, 'cache') folder = os.path.join(folder or request.folder, 'cache')
if not os.path.exists(folder): if not os.path.exists(folder):
os.mkdir(folder) os.mkdir(folder)
### we need this because of a possible bug in shelve that may self.storage = CacheOnDisk.PersistentStorage(folder)
### or may not lock
self.locker_name = os.path.join(folder, 'cache.lock') if not CacheAbstract.cache_stats_name in self.storage:
self.shelve_name = os.path.join(folder, 'cache.shelve') self.storage[CacheAbstract.cache_stats_name] = {'hit_total': 0, 'misses': 0}
def clear(self, regex=None):
self.initialize()
storage = self._open_shelve_and_lock()
try:
if regex is None:
storage.clear()
else:
self._clear(storage, regex)
storage.sync()
finally:
self._close_shelve_and_unlock()
def __call__(self, key, f, def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE): time_expire=DEFAULT_TIME_EXPIRE):
self.initialize() self.initialize()
dt = time_expire dt = time_expire
storage = self._open_shelve_and_lock() item = self.storage.get(key)
try: self.storage[CacheAbstract.cache_stats_name]['hit_total'] += 1
item = storage.get(key, None)
storage[CacheAbstract.cache_stats_name]['hit_total'] += 1 if item and f is None:
if item and f is None: del self.storage[key]
del storage[key]
storage.sync() if f is None:
now = time.time() return None
if f is None:
value = None now = time.time()
elif item and (dt is None or item[0] > now - dt):
value = item[1] if item and ((dt is None) or (item[0] > now - dt)):
else: value = item[1]
value = f() else:
storage[key] = (now, value) value = f()
storage[CacheAbstract.cache_stats_name]['misses'] += 1 self.storage[key] = (now, value)
storage.sync() self.storage[CacheAbstract.cache_stats_name]['misses'] += 1
finally:
self._close_shelve_and_unlock()
return value return value
def clear(self, regex=None):
self.initialize()
storage = self.storage
if regex is None:
storage.clear()
else:
self._clear(storage, regex)
if not CacheAbstract.cache_stats_name in storage:
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0, 'misses': 0}
def increment(self, key, value=1): def increment(self, key, value=1):
self.initialize() self.initialize()
storage = self._open_shelve_and_lock() self.storage[key] += value
try:
if key in storage:
value = storage[key][1] + value
storage[key] = (time.time(), value)
storage.sync()
finally:
self._close_shelve_and_unlock()
return value return value
class CacheAction(object): class CacheAction(object):
def __init__(self, func, key, time_expire, cache, cache_model): def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__ self.__name__ = func.__name__