# HG changeset patch # User Julien Cristau # Date 1395658643 -3600 # Node ID 46ed25d38fe2abf22457cdfc377168b84cee844e # Parent cbf4846d408a2fe9c4e776c3f878d7248caa90b1 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. diff -r cbf4846d408a -r 46ed25d38fe2 entities/test/unittest_wfobjs.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') diff -r cbf4846d408a -r 46ed25d38fe2 repoapi.py --- 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 diff -r cbf4846d408a -r 46ed25d38fe2 test/unittest_predicates.py --- 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") diff -r cbf4846d408a -r 46ed25d38fe2 test/unittest_rset.py --- 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 ' diff -r cbf4846d408a -r 46ed25d38fe2 web/application.py --- 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 diff -r cbf4846d408a -r 46ed25d38fe2 web/request.py --- 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]