Ticket #515: cache.patch

File cache.patch, 11.3 KB (added by eugene@…, 19 years ago)

patch for django/core/cache.py

  • django/core/cache.py

     
    1919                                    the same database/username as the rest of
    2020                                    the CMS, so only a table name is needed.)
    2121
    22     file:///var/tmp/django.cache/      A file-based cache at /var/tmp/django.cache
     22    file:///var/tmp/django.cache/   A file-based cache at /var/tmp/django.cache
    2323
    2424    simple:///                      A simple single-process memory cache; you
    2525                                    probably don't want to use this except for
     
    181181
    182182    def get(self, key, default=None):
    183183        now = time.time()
    184         exp = self._expire_info.get(key, now)
    185         if exp is not None and exp < now:
     184        exp = self._expire_info.get(key)
     185        if exp is None:
     186                        return default
     187        if exp < now:
    186188            del self._cache[key]
    187189            del self._expire_info[key]
    188190            return default
    189191        else:
    190             return self._cache.get(key, default)
     192            return self._cache[key]
    191193
    192194    def set(self, key, value, timeout=None):
    193195        if len(self._cache) >= self._max_entries:
     
    218220            doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
    219221            for k in doomed:
    220222                self.delete(k)
     223               
     224###############################
     225# Thread-safe in-memory cache #
     226###############################
    221227
     228import cPickle as pickle
     229from django.utils.synch import RWLock
     230
     231class _LocMemCache(_Cache):
     232    """Thread-safe in-memory cache"""
     233
     234    def __init__(self, host, params):
     235        _Cache.__init__(self, params)
     236        self.rw_lock = RWLock()
     237        self.cache = {}
     238        self.expire_info = {}
     239
     240        max_entries = params.get('max_entries', 300)
     241        try:
     242            self.max_entries = int(max_entries)
     243        except (ValueError, TypeError):
     244            self.max_entries = 300
     245
     246        cull_frequency = params.get('cull_frequency', 3)
     247        try:
     248            self.cull_frequency = int(cull_frequency)
     249        except (ValueError, TypeError):
     250            self.cull_frequency = 3
     251
     252    def get(self, key, default=None):
     253        delete = False
     254        self.rw_lock.reader_enters()
     255        try:
     256            now = time.time()
     257            exp = self.expire_info.get(key)
     258            if exp is None:
     259                return default
     260            if exp < now:
     261                delete = True
     262            else:
     263                return pickle.loads(self.cache[key])
     264                #return self.cache[key]
     265        finally:
     266            self.rw_lock.reader_leaves()
     267        if delete:
     268            self.rw_lock.writer_enters()
     269            try:
     270                del self.cache[key]
     271                del self.expire_info[key]
     272                return default
     273            finally:
     274                self.rw_lock.writer_leaves()
     275
     276    def set(self, key, value, timeout=None):
     277        if timeout is None:
     278            timeout = self.default_timeout
     279        self.rw_lock.writer_enters()
     280        try:
     281            if len(self.cache) >= self.max_entries:
     282                self._cull()
     283            self.cache[key] = pickle.dumps(value)
     284            #self.cache[key] = value
     285            self.expire_info[key] = time.time() + timeout
     286        finally:
     287            self.rw_lock.writer_leaves()
     288
     289    def delete(self, key):
     290        self.rw_lock.writer_enters()
     291        try:
     292            try:
     293                del self.cache[key]
     294            except KeyError:
     295                pass
     296            try:
     297                del self.expire_info[key]
     298            except KeyError:
     299                pass
     300        finally:
     301            self.rw_lock.writer_leaves()
     302
     303    def has_key(self, key):
     304        self.rw_lock.reader_enters()
     305        try:
     306            return self.cache.has_key(key)
     307        finally:
     308            self.rw_lock.reader_leaves()
     309
     310        # it is called from delete, which provides thread safety
     311    def _cull(self):
     312        if self.cull_frequency == 0:
     313            self.cache.clear()
     314            self.expire_info.clear()
     315        else:
     316            doomed = [k for (i, k) in enumerate(self.cache) if i % self.cull_frequency == 0]
     317            for k in doomed:
     318                self.delete(k)
     319
     320####################
     321# File-based cache #
     322####################
     323
     324import os, urllib
     325
     326class _FileCache(_Cache):
     327    """File-based cache"""
     328
     329    def __init__(self, dir, params):
     330        _Cache.__init__(self, params)
     331        self.dir = dir + '/'
     332
     333        max_entries = params.get('max_entries', 300)
     334        try:
     335            self.max_entries = int(max_entries)
     336        except (ValueError, TypeError):
     337            self.max_entries = 300
     338
     339        cull_frequency = params.get('cull_frequency', 3)
     340        try:
     341            self.cull_frequency = int(cull_frequency)
     342        except (ValueError, TypeError):
     343            self.cull_frequency = 3
     344
     345    def get(self, key, default=None):
     346        name = self.dir + urllib.quote_plus(key)
     347        try:
     348            f = file(name, 'r')
     349            exp = pickle.load(f)
     350            now = time.time()
     351            if exp < now:
     352                f.close()
     353                os.remove(name)
     354            else:
     355                return pickle.load(f)
     356        except:
     357            pass # silently eat any error
     358        return default
     359
     360    def set(self, key, value, timeout=None):
     361        name = self.dir + urllib.quote_plus(key)
     362        if timeout is None:
     363            timeout = self.default_timeout
     364        try:
     365            try:
     366                filelist = os.listdir(self.dir)
     367                if len(filelist) >= self.max_entries:
     368                    self._cull(filelist)
     369            except OSError:
     370                try:
     371                    os.makedirs(self.dir)
     372                except OSError:
     373                    pass
     374            f = file(name, 'w')
     375            now = time.time()
     376            pickle.dump(now + timeout, f)
     377            pickle.dump(value, f)
     378            f.close()
     379        except OSError:
     380            pass
     381
     382    def delete(self, key):
     383        name = self.dir + urllib.quote_plus(key)
     384        try:
     385            os.remove(name)
     386        except:
     387            pass    # silently skip
     388
     389    def has_key(self, key):
     390        name = urllib.quote_plus(self.dir + key)
     391        try:
     392            os.stat(name)
     393            return True
     394        except OSError:
     395            return False
     396
     397    # it is called from delete, which provides thread safety
     398    def _cull(self, filelist):
     399        if self.cull_frequency == 0:
     400            doomed = filelist
     401        else:
     402            doomed = [k for (i, k) in enumerate(filelist) if i % self.cull_frequency == 0]
     403        for name in doomed:
     404            try:
     405                os.remove(self.dir + name)
     406            except:
     407                pass # silently skip
     408
     409###################
     410# SQL-based cache #
     411###################
     412
     413from django.core import db
     414from django.conf import settings
     415from datetime import datetime
     416
     417class _SQLCache(_Cache):
     418    """SQL-based cache"""
     419
     420    def __init__(self, table, params):
     421        _Cache.__init__(self, params)
     422        self.table = table
     423
     424        max_entries = params.get('max_entries', 300)
     425        try:
     426            self.max_entries = int(max_entries)
     427        except (ValueError, TypeError):
     428            self.max_entries = 300
     429
     430        cull_frequency = params.get('cull_frequency', 3)
     431        try:
     432            self.cull_frequency = int(cull_frequency)
     433        except (ValueError, TypeError):
     434            self.cull_frequency = 3
     435
     436    def get(self, key, default=None):
     437        cursor = db.db.cursor()
     438        cursor.execute("SELECT * FROM %s WHERE keyx='%s'" % (self.table, key))
     439        if cursor.rowcount < 0:
     440            row = cursor.fetchone()
     441            if row is None:
     442                return default
     443        elif cursor.rowcount < 1:
     444            return default
     445        else:
     446            row = cursor.fetchone()
     447        now = datetime.now()
     448        if row[2] < now:
     449            cursor.execute("DELETE FROM %s WHERE keyx='%s'" % (self.table, key))
     450            db.db.commit()
     451            return default
     452        now = datetime.fromtimestamp(time.time())
     453        if settings.DATABASE_ENGINE == 'mysql':
     454            now = now.replace(microsecond=0)
     455        try:
     456            cursor.execute("UPDATE %s SET accessed='%s' WHERE keyx='%s'" % (self.table, now, key))
     457            db.db.commit()
     458        except:
     459            pass    # silently eat all exceptions
     460        return pickle.loads(row[4])
     461
     462    def set(self, key, value, timeout=None):
     463        if timeout is None:
     464            timeout = self.default_timeout
     465        cursor = db.db.cursor()
     466        # it should be done with stored procedure, which will update if necessary
     467        cursor.execute("SELECT COUNT(*) FROM %s" % self.table)
     468        now = datetime.fromtimestamp(time.time())
     469        exp = datetime.fromtimestamp(time.time() + timeout)
     470        # MySQL doesn't support microseconds
     471        if settings.DATABASE_ENGINE == 'mysql':
     472            now = now.replace(microsecond=0)
     473            exp = exp.replace(microsecond=0)
     474        num = cursor.fetchone()[0]
     475        if num >= self.max_entries:
     476            self._cull(cursor, now)
     477        try:
     478            cursor.execute("INSERT INTO %s (keyx,expiration,accessed,valx) VALUES('%s','%s','%s',%%s)" % (self.table, key, exp, now), [pickle.dumps(value)])
     479            db.db.commit()
     480        except:
     481            pass    # silently eat all exceptions
     482       
     483    def delete(self, key):
     484        cursor = db.db.cursor()
     485        try:
     486            cursor.execute("DELETE FROM %s WHERE keyx='%s'" % (self.table, key))
     487            db.db.commit()
     488        except:
     489            pass # silently eat all exceptions
     490
     491    def has_key(self, key):
     492        cursor = db.db.cursor()
     493        cursor.execute("SELECT id FROM %s WHERE keyx='%s'" % (self.table, key))
     494        if cursor.rowcount < 0:
     495            return cursor.fetchone() is not None
     496        else:
     497            return cursor.rowcount < 1
     498
     499    # it is called from delete, which provides thread safety
     500    def _cull(self, cursor, now):
     501        if self.cull_frequency == 0:
     502            cursor.execute("DELETE FROM %s" % self.table)
     503            db.db.commit()
     504            return
     505        cursor.execute("DELETE FROM %s WHERE expiration < '%s'" % (self.table, now))
     506        db.db.commit()
     507        cursor.execute("SELECT COUNT(*) FROM %s" % self.table)
     508        num = cursor.fetchone()[0]
     509        if num < self.max_entries:
     510            return
     511        cursor.execute("SELECT accessed FROM %s LIMIT 1 OFFSET %d" % (self.table, num / self.cull_frequency))
     512        if cursor.rowcount < 0:
     513            row = cursor.fetchone()
     514            if row is None:
     515                return
     516        elif cursor.rowcount < 1:
     517            return
     518        else:
     519            row = cursor.fetchone()
     520        cursor.execute("DELETE FROM %s WHERE accessed < '%s'" % (self.table, row[0]))
     521        db.db.commit()
     522
    222523##########################################
    223524# Read settings and load a cache backend #
    224525##########################################
     
    228529_BACKENDS = {
    229530    'memcached' : _MemcachedCache,
    230531    'simple'    : _SimpleCache,
     532    'locmem'    : _LocMemCache,
     533    'file'      : _FileCache,
     534    'sql'       : _SQLCache,
    231535}
    232536
    233537def get_cache(backend_uri):
Back to Top