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.
--- 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]