server/session.py
branchstable
changeset 9272 68744f5154c4
parent 9267 24d9b86dfa54
child 9274 b39ac464e3ac
equal deleted inserted replaced
9271:05aaa94c04f2 9272:68744f5154c4
   199     pass
   199     pass
   200 
   200 
   201 class CnxSetTracker(object):
   201 class CnxSetTracker(object):
   202     """Keep track of which transaction use which cnxset.
   202     """Keep track of which transaction use which cnxset.
   203 
   203 
   204     There should be one of this object per session plus one another for
   204     There should be one of these object per session (including internal sessions).
   205     internal session.
   205 
   206 
   206     Session objects are responsible of creating their CnxSetTracker object.
   207     Session object are responsible of creating their CnxSetTracker object.
   207 
   208 
   208     Transactions should use the :meth:`record` and :meth:`forget` to inform the
   209     Transaction should use the :meth:`record` and :meth:`forget` to inform the
   209     tracker of cnxsets they have acquired.
   210     tracker of cnxset they have acquired.
       
   211 
   210 
   212     .. automethod:: cubicweb.server.session.CnxSetTracker.record
   211     .. automethod:: cubicweb.server.session.CnxSetTracker.record
   213     .. automethod:: cubicweb.server.session.CnxSetTracker.forget
   212     .. automethod:: cubicweb.server.session.CnxSetTracker.forget
   214 
   213 
   215     Session use the :meth:`close` and :meth:`wait` method when closing.
   214     Sessions use the :meth:`close` and :meth:`wait` methods when closing.
   216 
   215 
   217     .. automethod:: cubicweb.server.session.CnxSetTracker.close
   216     .. automethod:: cubicweb.server.session.CnxSetTracker.close
   218     .. automethod:: cubicweb.server.session.CnxSetTracker.wait
   217     .. automethod:: cubicweb.server.session.CnxSetTracker.wait
   219 
   218 
   220     This object itself is threadsafe. It also requires caller to acquired its
   219     This object itself is threadsafe. It also requires caller to acquired its
   231 
   230 
   232     def __exit__(self, *args):
   231     def __exit__(self, *args):
   233         return self._condition.__exit__(*args)
   232         return self._condition.__exit__(*args)
   234 
   233 
   235     def record(self, txid, cnxset):
   234     def record(self, txid, cnxset):
   236         """Inform the tracker that a txid have acquired a cnxset
   235         """Inform the tracker that a txid has acquired a cnxset
   237 
   236 
   238         This methode is to be used by Transaction object.
   237         This method is to be used by Transaction objects.
   239 
   238 
   240         This method fails when:
   239         This method fails when:
   241         - The txid already have a recorded cnxset.
   240         - The txid already has a recorded cnxset.
   242         - The tracker is not active anymore.
   241         - The tracker is not active anymore.
   243 
   242 
   244         Notes about the caller:
   243         Notes about the caller:
   245         (1) It is responsible for retrieving a cnxset.
   244         (1) It is responsible for retrieving a cnxset.
   246         (2) It must be prepared to release the cnxset if the
   245         (2) It must be prepared to release the cnxset if the
   247             `cnxsettracker.forget` call fails.
   246             `cnxsettracker.forget` call fails.
   248         (3) It should acquire the tracker lock until the very end of the operation.
   247         (3) It should acquire the tracker lock until the very end of the operation.
   249         (4) However It take care to lock the CnxSetTracker object after having
   248         (4) However it must only lock the CnxSetTracker object after having
   250             retrieved the cnxset to prevent deadlock.
   249             retrieved the cnxset to prevent deadlock.
   251 
   250 
   252         A typical usage look like::
   251         A typical usage look like::
   253 
   252 
   254         cnxset = repo._get_cnxset() # (1)
   253         cnxset = repo._get_cnxset() # (1)
   259                 caller.cnxset = cnxset
   258                 caller.cnxset = cnxset
   260         except Exception:
   259         except Exception:
   261             repo._free_cnxset(cnxset) # (2)
   260             repo._free_cnxset(cnxset) # (2)
   262             raise
   261             raise
   263         """
   262         """
   264         # dubious since the caller is suppose to have acquired it anyway.
   263         # dubious since the caller is supposed to have acquired it anyway.
   265         with self._condition:
   264         with self._condition:
   266             if not self._active:
   265             if not self._active:
   267                 raise SessionClosedError('Closed')
   266                 raise SessionClosedError('Closed')
   268             old = self._record.get(txid)
   267             old = self._record.get(txid)
   269             if old is not None:
   268             if old is not None:
   270                 raise ValueError('"%s" already have a cnx_set (%r)'
   269                 raise ValueError('transaction "%s" already has a cnx_set (%r)'
   271                                  % (txid, old))
   270                                  % (txid, old))
   272             self._record[txid] = cnxset
   271             self._record[txid] = cnxset
   273 
   272 
   274     def forget(self, txid, cnxset):
   273     def forget(self, txid, cnxset):
   275         """Inform the tracker that a txid have release a cnxset
   274         """Inform the tracker that a txid have release a cnxset
   305             self._condition.notify_all()
   304             self._condition.notify_all()
   306 
   305 
   307     def close(self):
   306     def close(self):
   308         """Marks the tracker as inactive.
   307         """Marks the tracker as inactive.
   309 
   308 
   310         This methode is to be used by Session object.
   309         This method is to be used by Session objects.
   311 
   310 
   312         Inactive tracker does not accept new record anymore.
   311         An inactive tracker does not accept new records anymore.
   313         """
   312         """
   314         with self._condition:
   313         with self._condition:
   315             self._active = False
   314             self._active = False
   316 
   315 
   317     def wait(self, timeout=10):
   316     def wait(self, timeout=10):
   318         """Wait for all recorded cnxset to be released
   317         """Wait for all recorded cnxsets to be released
   319 
   318 
   320         This methode is to be used by Session object.
   319         This method is to be used by Session objects.
   321 
   320 
   322         returns a tuple of transaction id that remains open.
   321         Returns a tuple of transaction ids that remain open.
   323         """
   322         """
   324         with self._condition:
   323         with self._condition:
   325             if  self._active:
   324             if  self._active:
   326                 raise RuntimeError('Cannot wait on active tracker.'
   325                 raise RuntimeError('Cannot wait on active tracker.'
   327                                    ' Call tracker.close() first')
   326                                    ' Call tracker.close() first')
   334 class Transaction(object):
   333 class Transaction(object):
   335     """Repository Transaction
   334     """Repository Transaction
   336 
   335 
   337     Holds all transaction related data
   336     Holds all transaction related data
   338 
   337 
   339     Database connections resource:
   338     Database connection resources:
   340 
   339 
   341       :attr:`running_dbapi_query`, boolean flag telling if the executing query
   340       :attr:`running_dbapi_query`, boolean flag telling if the executing query
   342       is coming from a dbapi connection or is a query from within the repository
   341       is coming from a dbapi connection or is a query from within the repository
   343 
   342 
   344       :attr:`cnxset`, the connections set to use to execute queries on sources.
   343       :attr:`cnxset`, the connections set to use to execute queries on sources.
   345       If the transaction is read only, the connection set may be freed between
   344       If the transaction is read only, the connection set may be freed between
   346       actual query. This allows multiple transaction with a reasonable low
   345       actual queries. This allows multiple transactions with a reasonably low
   347       connection set pool size. control mechanism is detailed below
   346       connection set pool size.  Control mechanism is detailed below.
   348 
   347 
   349     .. automethod:: cubicweb.server.session.Transaction.set_cnxset
   348     .. automethod:: cubicweb.server.session.Transaction.set_cnxset
   350     .. automethod:: cubicweb.server.session.Transaction.free_cnxset
   349     .. automethod:: cubicweb.server.session.Transaction.free_cnxset
   351 
   350 
   352       :attr:`mode`, string telling the connections set handling mode, may be one
   351       :attr:`mode`, string telling the connections set handling mode, may be one
   355       'transaction' (we want to keep the connections set during all the
   354       'transaction' (we want to keep the connections set during all the
   356       transaction, with or without writing)
   355       transaction, with or without writing)
   357 
   356 
   358     Internal transaction data:
   357     Internal transaction data:
   359 
   358 
   360       :attr:`data`,is a dictionary containing some shared data
   359       :attr:`data` is a dictionary containing some shared data
   361       cleared at the end of the transaction. Hooks and operations may put
   360       cleared at the end of the transaction. Hooks and operations may put
   362       arbitrary data in there, and this may also be used as a communication
   361       arbitrary data in there, and this may also be used as a communication
   363       channel between the client and the repository.
   362       channel between the client and the repository.
   364 
   363 
   365       :attr:`pending_operations`, ordered list of operations to be processed on
   364       :attr:`pending_operations`, ordered list of operations to be processed on
   437 
   436 
   438     @property
   437     @property
   439     def transaction_data(self):
   438     def transaction_data(self):
   440         return self.data
   439         return self.data
   441 
   440 
   442 
       
   443     def clear(self):
   441     def clear(self):
   444         """reset internal data"""
   442         """reset internal data"""
   445         self.data = {}
   443         self.data = {}
   446         #: ordered list of operations to be processed on commit/rollback
   444         #: ordered list of operations to be processed on commit/rollback
   447         self.pending_operations = []
   445         self.pending_operations = []
   448         #: (None, 'precommit', 'postcommit', 'uncommitable')
   446         #: (None, 'precommit', 'postcommit', 'uncommitable')
   449         self.commit_state = None
   447         self.commit_state = None
   450         self.pruned_hooks_cache = {}
   448         self.pruned_hooks_cache = {}
       
   449 
   451     # Connection Set Management ###############################################
   450     # Connection Set Management ###############################################
   452     @property
   451     @property
   453     def cnxset(self):
   452     def cnxset(self):
   454         return self._cnxset
   453         return self._cnxset
   455 
   454 
   497                 self.repo._free_cnxset(cnxset)
   496                 self.repo._free_cnxset(cnxset)
   498 
   497 
   499 
   498 
   500     # Entity cache management #################################################
   499     # Entity cache management #################################################
   501     #
   500     #
   502     # The transaction entity cache as held in tx.data it is removed at end the
   501     # The transaction entity cache as held in tx.data is removed at the
   503     # end of the transaction (commit and rollback)
   502     # end of the transaction (commit and rollback)
   504     #
   503     #
   505     # XXX transaction level caching may be a pb with multiple repository
   504     # XXX transaction level caching may be a pb with multiple repository
   506     # instances, but 1. this is probably not the only one :$ and 2. it may be
   505     # instances, but 1. this is probably not the only one :$ and 2. it may be
   507     # an acceptable risk. Anyway we could activate it or not according to a
   506     # an acceptable risk. Anyway we could activate it or not according to a
   527         if eid is None:
   526         if eid is None:
   528             self.data.pop('ecache', None)
   527             self.data.pop('ecache', None)
   529         else:
   528         else:
   530             del self.data['ecache'][eid]
   529             del self.data['ecache'][eid]
   531 
   530 
   532     # Tracking of entity added of removed in the transaction ##################
   531     # Tracking of entities added of removed in the transaction ##################
   533     #
       
   534     # Those are function to  allows cheap call from client in other process.
       
   535 
   532 
   536     def deleted_in_transaction(self, eid):
   533     def deleted_in_transaction(self, eid):
   537         """return True if the entity of the given eid is being deleted in the
   534         """return True if the entity of the given eid is being deleted in the
   538         current transaction
   535         current transaction
   539         """
   536         """
   650 
   647 
   651     def transaction_inc_action_counter(self):
   648     def transaction_inc_action_counter(self):
   652         num = self.data.setdefault('tx_action_count', 0) + 1
   649         num = self.data.setdefault('tx_action_count', 0) + 1
   653         self.data['tx_action_count'] = num
   650         self.data['tx_action_count'] = num
   654         return num
   651         return num
       
   652 
   655     # db-api like interface ###################################################
   653     # db-api like interface ###################################################
   656 
   654 
   657     def source_defs(self):
   655     def source_defs(self):
   658         return self.repo.source_defs()
   656         return self.repo.source_defs()
   659 
   657 
   660     def describe(self, eid, asdict=False):
   658     def describe(self, eid, asdict=False):
   661         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
   659         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
   662         metas = self.repo.type_and_source_from_eid(eid, self)
   660         metas = self.repo.type_and_source_from_eid(eid, self)
   663         if asdict:
   661         if asdict:
   664             return dict(zip(('type', 'source', 'extid', 'asource'), metas))
   662             return dict(zip(('type', 'source', 'extid', 'asource'), metas))
   665        # XXX :-1 for cw compat, use asdict=True for full information
   663         # XXX :-1 for cw compat, use asdict=True for full information
   666         return metas[:-1]
   664         return metas[:-1]
   667 
       
   668 
   665 
   669     def source_from_eid(self, eid):
   666     def source_from_eid(self, eid):
   670         """return the source where the entity with id <eid> is located"""
   667         """return the source where the entity with id <eid> is located"""
   671         return self.repo.source_from_eid(eid, self)
   668         return self.repo.source_from_eid(eid, self)
   672 
   669 
   897         """return context manager to enter a transaction for the session: when
   894         """return context manager to enter a transaction for the session: when
   898         exiting the `with` block on exception, call `session.rollback()`, else
   895         exiting the `with` block on exception, call `session.rollback()`, else
   899         call `session.commit()` on normal exit.
   896         call `session.commit()` on normal exit.
   900 
   897 
   901         The `free_cnxset` will be given to rollback/commit methods to indicate
   898         The `free_cnxset` will be given to rollback/commit methods to indicate
   902         wether the connections set should be freed or not.
   899         whether the connections set should be freed or not.
   903         """
   900         """
   904         return transaction(self, free_cnxset)
   901         return transaction(self, free_cnxset)
   905 
   902 
   906 
   903 
   907     @deprecated('[3.17] do not use hijack_user. create new Session object')
   904     @deprecated('[3.17] do not use hijack_user. create new Session object')