Move entity cache from web.request.ConnectionCubicWebRequestBase to repoapi.ClientConnection
authorJulien Cristau <julien.cristau@logilab.fr>
Mon, 24 Mar 2014 11:57:23 +0100
changeset 9582 46ed25d38fe2
parent 9581 cbf4846d408a
child 9583 e337a9f658e0
Move entity cache from web.request.ConnectionCubicWebRequestBase to repoapi.ClientConnection ResultSet._build_entity relies on an entity cache to avoid going round in circles (stack overflow due to infinite recursion) e.g. in a postcreate script. Clear the cache at commit/rollback time to avoid unexpected side effects; and explicitly clear the cache in a few tests where we mix ORM and RQL using the same connection.
entities/test/unittest_wfobjs.py
repoapi.py
test/unittest_predicates.py
test/unittest_rset.py
web/application.py
web/request.py
--- a/entities/test/unittest_wfobjs.py	Mon Mar 24 16:38:57 2014 +0100
+++ b/entities/test/unittest_wfobjs.py	Mon Mar 24 11:57:23 2014 +0100
@@ -263,6 +263,7 @@
         state3 = mwf.add_state(u'state3')
         swftr1 = mwf.add_wftransition(u'swftr1', swf, state1,
                                       [(swfstate2, state2), (swfstate3, state3)])
+        swf.cw_clear_all_caches()
         self.assertEqual(swftr1.destination(None).eid, swfstate1.eid)
         # workflows built, begin test
         group = self.request().create_entity('CWGroup', name=u'grp1')
--- a/repoapi.py	Mon Mar 24 16:38:57 2014 +0100
+++ b/repoapi.py	Mon Mar 24 11:57:23 2014 +0100
@@ -154,6 +154,8 @@
         self._cnx = None
         self._open = None
         self._web_request = False
+        #: cache entities built during the connection
+        self._eid_cache = {}
         self.vreg = session.vreg
         self._set_user(session.user)
         self._autoclose_session = autoclose_session
@@ -216,8 +218,19 @@
         rset._rqlst = None
         return rset
 
-    commit = _srv_cnx_func('commit')
-    rollback = _srv_cnx_func('rollback')
+    @_open_only
+    def commit(self, *args, **kwargs):
+        try:
+            return self._cnx.commit(*args, **kwargs)
+        finally:
+            self.drop_entity_cache()
+
+    @_open_only
+    def rollback(self, *args, **kwargs):
+        try:
+            return self._cnx.rollback(*args, **kwargs)
+        finally:
+            self.drop_entity_cache()
 
     # security #################################################################
 
@@ -340,6 +353,25 @@
         # Connection object
         return self._cnx.repo.system_source.undo_transaction(self._cnx, txuuid)
 
+    # cache management
+
+    def entity_cache(self, eid):
+        return self._eid_cache[eid]
+
+    def set_entity_cache(self, entity):
+        self._eid_cache[entity.eid] = entity
+
+    def cached_entities(self):
+        return self._eid_cache.values()
+
+    def drop_entity_cache(self, eid=None):
+        if eid is None:
+            self._eid_cache = {}
+        else:
+            del self._eid_cache[eid]
+
+    # deprecated stuff
+
     @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one')
     def request(self):
         return self
--- a/test/unittest_predicates.py	Mon Mar 24 16:38:57 2014 +0100
+++ b/test/unittest_predicates.py	Mon Mar 24 11:57:23 2014 +0100
@@ -19,6 +19,7 @@
 
 from operator import eq, lt, le, gt
 from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.decorators import clear_cache
 
 from cubicweb import Binary
 from cubicweb.devtools.testlib import CubicWebTC
@@ -96,6 +97,8 @@
         self._commit()
         self.assertEqual(self.adapter.state, 'validated')
 
+        clear_cache(self.rset, 'get_entity')
+
         selector = is_in_state('created')
         self.assertEqual(selector(None, self.req, rset=self.rset), 0)
         selector = is_in_state('validated')
@@ -109,6 +112,8 @@
         self._commit()
         self.assertEqual(self.adapter.state, 'abandoned')
 
+        clear_cache(self.rset, 'get_entity')
+
         selector = is_in_state('created')
         self.assertEqual(selector(None, self.req, rset=self.rset), 0)
         selector = is_in_state('validated')
@@ -139,6 +144,8 @@
         self._commit()
         self.assertEqual(self.adapter.state, 'validated')
 
+        clear_cache(self.rset, 'get_entity')
+
         selector = on_transition("validate")
         self.assertEqual(selector(None, self.req, rset=self.rset), 1)
         selector = on_transition("validate", "forsake")
@@ -150,6 +157,8 @@
         self._commit()
         self.assertEqual(self.adapter.state, 'abandoned')
 
+        clear_cache(self.rset, 'get_entity')
+
         selector = on_transition("validate")
         self.assertEqual(selector(None, self.req, rset=self.rset), 0)
         selector = on_transition("validate", "forsake")
--- a/test/unittest_rset.py	Mon Mar 24 16:38:57 2014 +0100
+++ b/test/unittest_rset.py	Mon Mar 24 11:57:23 2014 +0100
@@ -275,6 +275,7 @@
     def test_get_entity_simple(self):
         self.request().create_entity('CWUser', login=u'adim', upassword='adim',
                                      surname=u'di mascio', firstname=u'adrien')
+        self.cnx.drop_entity_cache()
         e = self.execute('Any X,T WHERE X login "adim", X surname T').get_entity(0, 0)
         self.assertEqual(e.cw_attr_cache['surname'], 'di mascio')
         self.assertRaises(KeyError, e.cw_attr_cache.__getitem__, 'firstname')
@@ -286,6 +287,7 @@
 
     def test_get_entity_advanced(self):
         self.request().create_entity('Bookmark', title=u'zou', path=u'/view')
+        self.cnx.drop_entity_cache()
         self.execute('SET X bookmarked_by Y WHERE X is Bookmark, Y login "anon"')
         rset = self.execute('Any X,Y,XT,YN WHERE X bookmarked_by Y, X title XT, Y login YN')
 
@@ -357,6 +359,7 @@
 
     def test_get_entity_union(self):
         e = self.request().create_entity('Bookmark', title=u'manger', path=u'path')
+        self.cnx.drop_entity_cache()
         rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
                             '((Any X,N WHERE X is Bookmark, X title N)'
                             ' UNION '
--- a/web/application.py	Mon Mar 24 16:38:57 2014 +0100
+++ b/web/application.py	Mon Mar 24 11:57:23 2014 +0100
@@ -507,9 +507,6 @@
                     req.cnx.rollback()
                 except Exception:
                     pass # ignore rollback error at this point
-            # request may be referenced by "onetime callback", so clear its entity
-            # cache to avoid memory usage
-            req.drop_entity_cache()
         self.add_undo_link_to_msg(req)
         self.debug('query %s executed in %s sec', req.relative_path(), clock() - tstart)
         return result
--- a/web/request.py	Mon Mar 24 16:38:57 2014 +0100
+++ b/web/request.py	Mon Mar 24 11:57:23 2014 +0100
@@ -1025,8 +1025,6 @@
         from cubicweb.dbapi import DBAPISession, _NeedAuthAccessMock
         self.session = DBAPISession(None)
         self.cnx = self.user = _NeedAuthAccessMock()
-        #: cache entities built during the request
-        self._eid_cache = {}
 
     def set_cnx(self, cnx):
         self.cnx = cnx
@@ -1070,20 +1068,11 @@
 
     # entities cache management ###############################################
 
-    def entity_cache(self, eid):
-        return self._eid_cache[eid]
-
-    def set_entity_cache(self, entity):
-        self._eid_cache[entity.eid] = entity
+    entity_cache = _cnx_func('entity_cache')
+    set_entity_cache = _cnx_func('set_entity_cache')
+    cached_entities = _cnx_func('cached_entities')
+    drop_entity_cache = _cnx_func('drop_entity_cache')
 
-    def cached_entities(self):
-        return self._eid_cache.values()
-
-    def drop_entity_cache(self, eid=None):
-        if eid is None:
-            self._eid_cache = {}
-        else:
-            del self._eid_cache[eid]