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)

Reply via email to