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') |