server/session.py
changeset 9098 5467fb901931
parent 9095 6656fafd96c8
child 9102 c9ed52e53526
--- a/server/session.py	Thu Jun 27 10:44:40 2013 +0200
+++ b/server/session.py	Wed Jun 26 11:21:39 2013 +0200
@@ -24,6 +24,8 @@
 from uuid import uuid4
 from warnings import warn
 import json
+import functools
+from contextlib import contextmanager
 
 from logilab.common.deprecation import deprecated
 from logilab.common.textutils import unormalize
@@ -360,6 +362,16 @@
                 timeout -= time() - start
             return tuple(self._record)
 
+
+def _with_cnx_set(func):
+    """decorator for Connection method that ensure they run with a cnxset """
+    @functools.wraps(func)
+    def wrapper(cnx, *args, **kwargs):
+        with cnx.ensure_cnx_set:
+            return func(cnx, *args, **kwargs)
+    return wrapper
+
+
 class Connection(RequestSessionBase):
     """Repository Connection
 
@@ -426,6 +438,12 @@
         self.connectionid = cnxid
         #: reentrance handling
         self.ctx_count = 0
+        #: count the number of entry in a context needing a cnxset
+        self._cnxset_count = 0
+        #: Boolean for compat with the older explicite set_cnxset/free_cnx API
+        #: When a call set_cnxset is done, no automatic freeing will be done
+        #: until free_cnx is called.
+        self._auto_free_cnx_set = True
 
         #: server.Repository object
         self.repo = session.repo
@@ -541,7 +559,7 @@
                 self._cnxset = new_cnxset
                 self.ctx_count += 1
 
-    def set_cnxset(self):
+    def _set_cnxset(self):
         """the connection need a connections set to execute some queries"""
         if self.cnxset is None:
             cnxset = self.repo._get_cnxset()
@@ -557,7 +575,7 @@
                 raise
         return self.cnxset
 
-    def free_cnxset(self, ignoremode=False):
+    def _free_cnxset(self, ignoremode=False):
         """the connection is no longer using its connections set, at least for some time"""
         # cnxset may be none if no operation has been done since last commit
         # or rollback
@@ -569,6 +587,29 @@
                 cnxset.cnxset_freed()
                 self.repo._free_cnxset(cnxset)
 
+    def set_cnxset(self):
+        self._auto_free_cnx_set = False
+        return self._set_cnxset()
+
+    def free_cnxset(self, ignoremode=False):
+        self._auto_free_cnx_set = True
+        return self._free_cnxset(ignoremode=ignoremode)
+
+
+    @property
+    @contextmanager
+    def ensure_cnx_set(self):
+        assert self._cnxset_count >= 0
+        if self._cnxset_count == 0:
+            self._set_cnxset()
+        try:
+            self._cnxset_count += 1
+            yield
+        finally:
+            self._cnxset_count = max(self._cnxset_count - 1, 0)
+            if self._cnxset_count == 0 and self._auto_free_cnx_set:
+                self._free_cnxset()
+
 
     # Entity cache management #################################################
     #
@@ -869,6 +910,7 @@
     def source_defs(self):
         return self.repo.source_defs()
 
+    @_with_cnx_set
     def describe(self, eid, asdict=False):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         metas = self.repo.type_and_source_from_eid(eid, self)
@@ -877,13 +919,14 @@
        # XXX :-1 for cw compat, use asdict=True for full information
         return metas[:-1]
 
-
+    @_with_cnx_set
     def source_from_eid(self, eid):
         """return the source where the entity with id <eid> is located"""
         return self.repo.source_from_eid(eid, self)
 
     # core method #############################################################
 
+    @_with_cnx_set
     def execute(self, rql, kwargs=None, eid_key=None, build_descr=True):
         """db-api like method directly linked to the querier execute method.
 
@@ -1018,24 +1061,22 @@
 
     # resource accessors ######################################################
 
+    @_with_cnx_set
     def call_service(self, regid, **kwargs):
         json.dumps(kwargs) # This line ensure that people use serialisable
                            # argument for call service. this is very important
                            # to enforce that from start to make sure RPC
                            # version is available.
         self.info('calling service %s', regid)
-        self.set_cnxset()
-        try:
-            service = self.vreg['services'].select(regid, self, **kwargs)
-            result = service.call(**kwargs)
-            json.dumps(result) # This line ensure that service have serialisable
-                               # output. this is very important to enforce that
-                               # from start to make sure RPC version is
-                               # available.
-            return result
-        finally:
-            self.free_cnxset()
+        service = self.vreg['services'].select(regid, self, **kwargs)
+        result = service.call(**kwargs)
+        json.dumps(result) # This line ensure that service have serialisable
+                           # output. this is very important to enforce that
+                           # from start to make sure RPC version is
+                           # available.
+        return result
 
+    @_with_cnx_set
     def system_sql(self, sql, args=None, rollback_on_failure=True):
         """return a sql cursor on the system database"""
         if sql.split(None, 1)[0].upper() != 'SELECT':
@@ -1385,6 +1426,7 @@
                 raise SessionClosedError('try to set connections set on a closed session %s' % self.id)
             return self._cnx.set_cnxset()
     free_cnxset = cnx_meth('free_cnxset')
+    ensure_cnx_set = cnx_attr('ensure_cnx_set')
 
     def _touch(self):
         """update latest session usage timestamp and reset mode to read"""