changeset d3af4b3aa7f5 in tryton:default
details: https://hg.tryton.org/tryton?cmd=changeset;node=d3af4b3aa7f5
description:
Add cache management on RPC
issue8233
review257231002
diffstat:
tryton/gui/window/view_form/screen/screen.py | 2 +
tryton/gui/window/win_export.py | 6 +-
tryton/jsonrpc.py | 77 ++++++++++++++++++++++++++-
tryton/rpc.py | 40 +------------
4 files changed, 82 insertions(+), 43 deletions(-)
diffs (290 lines):
diff -r f5ec90475d69 -r d3af4b3aa7f5
tryton/gui/window/view_form/screen/screen.py
--- a/tryton/gui/window/view_form/screen/screen.py Sun Apr 07 19:50:05
2019 +0200
+++ b/tryton/gui/window/view_form/screen/screen.py Mon Apr 08 10:10:40
2019 +0200
@@ -26,6 +26,7 @@
node_attributes, sur, RPCContextReload, warning
from tryton.action import Action
from tryton.pyson import PYSONDecoder
+from tryton.rpc import clear_cache
_ = gettext.gettext
logger = logging.getLogger(__name__)
@@ -845,6 +846,7 @@
self.model_name, json_domain, view.children_field,
json_paths, json_selected_path,
process_exception=False)
+ clear_cache('model.ir.ui.view_tree_state.get')
except Exception:
logger.warn(
_('Unable to set view tree state'), exc_info=True)
diff -r f5ec90475d69 -r d3af4b3aa7f5 tryton/gui/window/win_export.py
--- a/tryton/gui/window/win_export.py Sun Apr 07 19:50:05 2019 +0200
+++ b/tryton/gui/window/win_export.py Mon Apr 08 10:10:40 2019 +0200
@@ -13,7 +13,7 @@
import tryton.common as common
from tryton.common import RPCExecute, RPCException
from tryton.gui.window.win_csv import WinCSV
-from tryton.rpc import clear_toolbar_cache
+from tryton.rpc import clear_cache
_ = gettext.gettext
@@ -217,7 +217,7 @@
context=self.context)
except RPCException:
return
- clear_toolbar_cache()
+ clear_cache('model.%s.view_toolbar_get' % self.model)
if iter_ is None:
self.predef_model.append((new_id, fields, name))
else:
@@ -237,7 +237,7 @@
context=self.context)
except RPCException:
return
- clear_toolbar_cache()
+ clear_cache('model.%s.view_toolbar_get' % self.model)
for i in range(len(self.predef_model)):
if self.predef_model[i][0] == export_id:
del self.predef_model[i]
diff -r f5ec90475d69 -r d3af4b3aa7f5 tryton/jsonrpc.py
--- a/tryton/jsonrpc.py Sun Apr 07 19:50:05 2019 +0200
+++ b/tryton/jsonrpc.py Mon Apr 08 10:10:40 2019 +0200
@@ -11,7 +11,9 @@
import base64
import threading
import errno
+import logging
from functools import partial
+from collections import defaultdict
from contextlib import contextmanager
from functools import reduce
from urllib.parse import urljoin
@@ -20,6 +22,7 @@
"ServerProxy", "ServerPool"]
CONNECT_TIMEOUT = 5
DEFAULT_TIMEOUT = None
+logger = logging.getLogger(__name__)
class ResponseError(xmlrpc.client.ResponseError):
@@ -128,7 +131,8 @@
accept_gzip_encoding = True
encode_threshold = 1400 # common MTU
- def __init__(self, fingerprints=None, ca_certs=None, session=None):
+ def __init__(
+ self, fingerprints=None, ca_certs=None, session=None):
xmlrpc.client.Transport.__init__(self)
self._connection = (None, None)
self.__fingerprints = fingerprints
@@ -140,6 +144,18 @@
parser = JSONParser(target)
return parser, target
+ def parse_response(self, response):
+ cache = None
+ if hasattr(response, 'getheader'):
+ cache = int(response.getheader('X-Tryton-Cache', 0))
+ response = super().parse_response(response)
+ if cache:
+ try:
+ response['cache'] = int(cache)
+ except ValueError:
+ pass
+ return response
+
def get_host_info(self, host):
host, extra_headers, x509 = xmlrpc.client.Transport.get_host_info(
self, host)
@@ -231,7 +247,7 @@
__id = 0
def __init__(self, host, port, database='', verbose=0,
- fingerprints=None, ca_certs=None, session=None):
+ fingerprints=None, ca_certs=None, session=None, cache=None):
self.__host = '%s:%s' % (host, port)
if database:
self.__handler = '/%s/' % database
@@ -239,15 +255,22 @@
self.__handler = '/'
self.__transport = Transport(fingerprints, ca_certs, session)
self.__verbose = verbose
+ self.__cache = cache
def __request(self, methodname, params):
+ dumper = partial(json.dumps, cls=JSONEncoder, separators=(',', ':'))
self.__id += 1
id_ = self.__id
- request = json.dumps({
+ if self.__cache and self.__cache.cached(methodname):
+ try:
+ return self.__cache.get(methodname, dumper(params))
+ except KeyError:
+ pass
+ request = dumper({
'id': id_,
'method': methodname,
'params': params,
- }, cls=JSONEncoder, separators=(',', ':')).encode('utf-8')
+ }).encode('utf-8')
try:
try:
@@ -274,12 +297,15 @@
except Exception:
self.__transport.close()
raise
-
if response['id'] != id_:
raise ResponseError('Invalid response id (%s) excpected %s' %
(response['id'], id_))
if response.get('error'):
raise Fault(*response['error'])
+ if self.__cache and response.get('cache'):
+ self.__cache.set(
+ methodname, dumper(params), response['cache'],
+ response['result'])
return response['result']
def close(self):
@@ -293,8 +319,11 @@
class ServerPool(object):
keep_max = 4
+ _cache = None
def __init__(self, host, port, database, *args, **kwargs):
+ if kwargs.get('cache'):
+ self._cache = kwargs['cache'] = _Cache()
self.ServerProxy = partial(
ServerProxy, host, port, database, *args, **kwargs)
@@ -353,3 +382,41 @@
conn = self.getconn()
yield conn
self.putconn(conn)
+
+ def clear_cache(self, prefix=None):
+ if self._cache:
+ self._cache.clear(prefix)
+
+
+class _Cache:
+
+ def __init__(self):
+ self.store = defaultdict(dict)
+
+ def cached(self, prefix):
+ return prefix in self.store
+
+ def set(self, prefix, key, expire, value):
+ if isinstance(expire, (int, float)):
+ expire = datetime.timedelta(seconds=expire)
+ if isinstance(expire, datetime.timedelta):
+ expire = datetime.datetime.now() + expire
+ self.store[prefix][key] = (expire, value)
+
+ def get(self, prefix, key):
+ now = datetime.datetime.now()
+ try:
+ expire, value = self.store[prefix][key]
+ except ValueError:
+ raise KeyError
+ if expire < now:
+ self.store.pop(key)
+ raise KeyError
+ logger.info('(cached) %s %s', prefix, key)
+ return value
+
+ def clear(self, prefix=None):
+ if prefix:
+ self.store[prefix].clear()
+ else:
+ self.store.clear()
diff -r f5ec90475d69 -r d3af4b3aa7f5 tryton/rpc.py
--- a/tryton/rpc.py Sun Apr 07 19:50:05 2019 +0200
+++ b/tryton/rpc.py Mon Apr 08 10:10:40 2019 +0200
@@ -75,7 +75,6 @@
def login(parameters):
from tryton import common
global CONNECTION, _USER
- global _VIEW_CACHE, _TOOLBAR_CACHE, _KEYWORD_CACHE
host = CONFIG['login.host']
hostname = common.get_hostname(host)
port = common.get_port(host)
@@ -91,17 +90,13 @@
session = ':'.join(map(str, [username] + result))
if CONNECTION is not None:
CONNECTION.close()
- CONNECTION = ServerPool(hostname, port, database, session=session)
- _VIEW_CACHE = {}
- _TOOLBAR_CACHE = {}
- _KEYWORD_CACHE = {}
-
+ CONNECTION = ServerPool(
+ hostname, port, database, session=session, cache=not CONFIG['dev'])
bus.listen(CONNECTION)
def logout():
global CONNECTION, _USER
- global _VIEW_CACHE, _TOOLBAR_CACHE, _KEYWORD_CACHE
if CONNECTION is not None:
try:
logging.getLogger(__name__).info('common.db.logout()')
@@ -112,31 +107,12 @@
CONNECTION.close()
CONNECTION = None
_USER = None
- _VIEW_CACHE = {}
- _TOOLBAR_CACHE = {}
- _KEYWORD_CACHE = {}
def _execute(blocking, *args):
global CONNECTION, _USER
if CONNECTION is None:
raise TrytonServerError('403')
- key = False
- model = args[1]
- method = args[2]
- if not CONFIG['dev']:
- if method == 'fields_view_get':
- key = str(args)
- if key in _VIEW_CACHE:
- return _VIEW_CACHE[key]
- elif method == 'view_toolbar_get':
- key = str(args)
- if key in _TOOLBAR_CACHE:
- return _TOOLBAR_CACHE[key]
- elif model == 'ir.action.keyword' and method == 'get_keyword':
- key = str(args)
- if key in _KEYWORD_CACHE:
- return _KEYWORD_CACHE[key]
try:
name = '.'.join(args[:3])
args = args[3:]
@@ -145,13 +121,6 @@
result = getattr(conn, name)(*args)
except (http.client.CannotSendRequest, socket.error) as exception:
raise TrytonServerUnavailable(*exception.args)
- if not CONFIG['dev']:
- if key and method == 'fields_view_get':
- _VIEW_CACHE[key] = result
- elif key and method == 'view_toolbar_get':
- _TOOLBAR_CACHE[key] = result
- elif key and model == 'ir.action.keyword' and method == 'get_keyword':
- _KEYWORD_CACHE[key] = result
logging.getLogger(__name__).debug(repr(result))
return result
@@ -164,5 +133,6 @@
return _execute(False, *args)
-def clear_toolbar_cache():
- _TOOLBAR_CACHE.clear()
+def clear_cache(prefix=None):
+ if CONNECTION:
+ CONNECTION.clear_cache(prefix)