170 lines
5.8 KiB
Python
170 lines
5.8 KiB
Python
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
#this repository contains the full copyright notices and license terms.
|
|
import inspect
|
|
import time
|
|
import datetime
|
|
from threading import Lock
|
|
from trytond.transaction import Transaction
|
|
from trytond.config import CONFIG
|
|
from trytond.backend import Database
|
|
|
|
|
|
class Cache(object):
|
|
"""
|
|
Use it as a decorator of the function you plan to cache
|
|
Timeout: 0 = no timeout, otherwise in seconds
|
|
"""
|
|
_cache_instance = []
|
|
_resets = {}
|
|
_resets_lock = Lock()
|
|
|
|
def __init__(self, name, timeout=3600, max_len=1024):
|
|
self.timeout = timeout
|
|
self.max_len = max_len
|
|
self._cache = {}
|
|
self._cache_instance.append(self)
|
|
self._name = name
|
|
self._timestamp = None
|
|
self._lock = Lock()
|
|
|
|
def __call__(self, function):
|
|
arg_names = inspect.getargspec(function)[0]
|
|
|
|
def reset():
|
|
dbname = Transaction().cursor.dbname
|
|
Cache.reset(dbname, self._name)
|
|
self._lock.acquire()
|
|
try:
|
|
self._cache[dbname] = {}
|
|
finally:
|
|
self._lock.release()
|
|
return True
|
|
|
|
def call(model, *args, **kwargs):
|
|
result = None
|
|
find = False
|
|
cursor = Transaction().cursor
|
|
# Update named arguments with positional argument values
|
|
kwargs_origin = kwargs.copy()
|
|
kwargs.update(dict(zip(arg_names, args)))
|
|
kwargs = kwargs.items()
|
|
kwargs.sort()
|
|
|
|
self._lock.acquire()
|
|
try:
|
|
self._cache.setdefault(cursor.dbname, {})
|
|
finally:
|
|
self._lock.release()
|
|
|
|
lower = None
|
|
self._lock.acquire()
|
|
try:
|
|
if len(self._cache[cursor.dbname]) > self.max_len:
|
|
mintime = time.time() - self.timeout
|
|
for key in self._cache[cursor.dbname].keys():
|
|
last_time = self._cache[cursor.dbname][key][1]
|
|
if mintime > last_time:
|
|
del self._cache[cursor.dbname][key]
|
|
else:
|
|
if not lower or lower[1] > last_time:
|
|
lower = (key, last_time)
|
|
if (len(self._cache[cursor.dbname]) > self.max_len
|
|
and lower):
|
|
del self._cache[cursor.dbname][lower[0]]
|
|
finally:
|
|
self._lock.release()
|
|
|
|
# Work out key as a tuple
|
|
key = (id(model), Transaction().user, repr(Transaction().context),
|
|
repr(kwargs))
|
|
|
|
# Check cache and return cached value if possible
|
|
self._lock.acquire()
|
|
try:
|
|
if key in self._cache[cursor.dbname]:
|
|
(value, last_time) = self._cache[cursor.dbname][key]
|
|
mintime = time.time() - self.timeout
|
|
if self.timeout <= 0 or mintime <= last_time:
|
|
self._cache[cursor.dbname][key] = (value,
|
|
time.time())
|
|
result = value
|
|
find = True
|
|
finally:
|
|
self._lock.release()
|
|
|
|
if not find:
|
|
# Work out new value, cache it and return it
|
|
result = function(model, *args, **kwargs_origin)
|
|
|
|
self._lock.acquire()
|
|
try:
|
|
self._cache[cursor.dbname][key] = (result, time.time())
|
|
finally:
|
|
self._lock.release()
|
|
return result
|
|
|
|
call.__doc__ = function.__doc__
|
|
call.reset = reset
|
|
return call
|
|
|
|
@staticmethod
|
|
def clean(dbname):
|
|
if not CONFIG['multi_server']:
|
|
return
|
|
database = Database(dbname).connect()
|
|
cursor = database.cursor()
|
|
try:
|
|
cursor.execute('SELECT "timestamp", "name" FROM ir_cache')
|
|
timestamps = {}
|
|
for timestamp, name in cursor.fetchall():
|
|
timestamps[name] = timestamp
|
|
finally:
|
|
cursor.commit()
|
|
cursor.close()
|
|
for obj in Cache._cache_instance:
|
|
if obj._name in timestamps:
|
|
if not obj._timestamp or timestamps[obj._name] > obj._timestamp:
|
|
obj._timestamp = timestamps[obj._name]
|
|
obj._lock.acquire()
|
|
try:
|
|
obj._cache[dbname] = {}
|
|
finally:
|
|
obj._lock.release()
|
|
|
|
@staticmethod
|
|
def reset(dbname, name):
|
|
if not CONFIG['multi_server']:
|
|
return
|
|
Cache._resets_lock.acquire()
|
|
try:
|
|
Cache._resets.setdefault(dbname, set())
|
|
Cache._resets[dbname].add(name)
|
|
finally:
|
|
Cache._resets_lock.release()
|
|
return
|
|
|
|
@staticmethod
|
|
def resets(dbname):
|
|
if not CONFIG['multi_server']:
|
|
return
|
|
database = Database(dbname).connect()
|
|
cursor = database.cursor()
|
|
Cache._resets_lock.acquire()
|
|
Cache._resets.setdefault(dbname, set())
|
|
try:
|
|
for name in Cache._resets[dbname]:
|
|
cursor.execute('SELECT name FROM ir_cache WHERE name = %s',
|
|
(name,))
|
|
if cursor.fetchone():
|
|
cursor.execute('UPDATE ir_cache SET "timestamp" = %s '\
|
|
'WHERE name = %s', (datetime.datetime.now(), name))
|
|
else:
|
|
cursor.execute('INSERT INTO ir_cache ' \
|
|
'("timestamp", "name") ' \
|
|
'VALUES (%s, %s)', (datetime.datetime.now(), name))
|
|
Cache._resets[dbname].clear()
|
|
finally:
|
|
cursor.commit()
|
|
cursor.close()
|
|
Cache._resets_lock.release()
|