270 :attr:`_threads_in_transaction` is a set of (thread, connections set) |
270 :attr:`_threads_in_transaction` is a set of (thread, connections set) |
271 referencing threads that currently hold a connections set for the session. |
271 referencing threads that currently hold a connections set for the session. |
272 .. automethod:: cubicweb.server.session.transaction |
272 .. automethod:: cubicweb.server.session.transaction |
273 |
273 |
274 You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`, |
274 You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`, |
275 simply access transaction data transparently through the :attr:`_threaddata` |
275 simply access transaction data transparently through the :attr:`_tx` |
276 property. Also, you usually don't have to access it directly since current |
276 property. Also, you usually don't have to access it directly since current |
277 transaction's data may be accessed/modified through properties / methods: |
277 transaction's data may be accessed/modified through properties / methods: |
278 |
278 |
279 :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary |
279 :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary |
280 containing some shared data that should be cleared at the end of the |
280 containing some shared data that should be cleared at the end of the |
427 |
427 |
428 |
428 |
429 def hijack_user(self, user): |
429 def hijack_user(self, user): |
430 """return a fake request/session using specified user""" |
430 """return a fake request/session using specified user""" |
431 session = Session(user, self.repo) |
431 session = Session(user, self.repo) |
432 threaddata = session._threaddata |
432 tx = session._tx |
433 threaddata.cnxset = self.cnxset |
433 tx.cnxset = self.cnxset |
434 # we attributed a connections set, need to update ctx_count else it will be freed |
434 # we attributed a connections set, need to update ctx_count else it will be freed |
435 # while undesired |
435 # while undesired |
436 threaddata.ctx_count = 1 |
436 tx.ctx_count = 1 |
437 # share pending_operations, else operation added in the hi-jacked |
437 # share pending_operations, else operation added in the hi-jacked |
438 # session such as SendMailOp won't ever be processed |
438 # session such as SendMailOp won't ever be processed |
439 threaddata.pending_operations = self.pending_operations |
439 tx.pending_operations = self.pending_operations |
440 # everything in transaction_data should be copied back but the entity |
440 # everything in transaction_data should be copied back but the entity |
441 # type cache we don't want to avoid security pb |
441 # type cache we don't want to avoid security pb |
442 threaddata.transaction_data = self.transaction_data.copy() |
442 tx.transaction_data = self.transaction_data.copy() |
443 threaddata.transaction_data.pop('ecache', None) |
443 tx.transaction_data.pop('ecache', None) |
444 return session |
444 return session |
445 |
445 |
446 def add_relation(self, fromeid, rtype, toeid): |
446 def add_relation(self, fromeid, rtype, toeid): |
447 """provide direct access to the repository method to add a relation. |
447 """provide direct access to the repository method to add a relation. |
448 |
448 |
617 oldread = self.set_read_security(read) |
617 oldread = self.set_read_security(read) |
618 if write is None: |
618 if write is None: |
619 oldwrite = None |
619 oldwrite = None |
620 else: |
620 else: |
621 oldwrite = self.set_write_security(write) |
621 oldwrite = self.set_write_security(write) |
622 self._threaddata.ctx_count += 1 |
622 self._tx.ctx_count += 1 |
623 return oldread, oldwrite |
623 return oldread, oldwrite |
624 |
624 |
625 def reset_security(self, read, write): |
625 def reset_security(self, read, write): |
626 txstore = self._threaddata |
626 txstore = self._tx |
627 txstore.ctx_count -= 1 |
627 txstore.ctx_count -= 1 |
628 if txstore.ctx_count == 0: |
628 if txstore.ctx_count == 0: |
629 self._clear_thread_storage(txstore) |
629 self._clear_thread_storage(txstore) |
630 else: |
630 else: |
631 if read is not None: |
631 if read is not None: |
700 """return a boolean telling if it's triggered by a db-api query or by |
700 """return a boolean telling if it's triggered by a db-api query or by |
701 a session query. |
701 a session query. |
702 |
702 |
703 To be used in hooks, else may have a wrong value. |
703 To be used in hooks, else may have a wrong value. |
704 """ |
704 """ |
705 return getattr(self._threaddata, 'dbapi_query', True) |
705 return getattr(self._tx, 'dbapi_query', True) |
706 |
706 |
707 # hooks activation control ################################################# |
707 # hooks activation control ################################################# |
708 # all hooks should be activated during normal execution |
708 # all hooks should be activated during normal execution |
709 |
709 |
710 def allow_all_hooks_but(self, *categories): |
710 def allow_all_hooks_but(self, *categories): |
712 def deny_all_hooks_but(self, *categories): |
712 def deny_all_hooks_but(self, *categories): |
713 return hooks_control(self, HOOKS_DENY_ALL, *categories) |
713 return hooks_control(self, HOOKS_DENY_ALL, *categories) |
714 |
714 |
715 @property |
715 @property |
716 def hooks_mode(self): |
716 def hooks_mode(self): |
717 return self._threaddata.hooks_mode |
717 return self._tx.hooks_mode |
718 |
718 |
719 def set_hooks_mode(self, mode): |
719 def set_hooks_mode(self, mode): |
720 assert mode is HOOKS_ALLOW_ALL or mode is HOOKS_DENY_ALL |
720 assert mode is HOOKS_ALLOW_ALL or mode is HOOKS_DENY_ALL |
721 oldmode = self._threaddata.hooks_mode |
721 oldmode = self._tx.hooks_mode |
722 self._threaddata.hooks_mode = mode |
722 self._tx.hooks_mode = mode |
723 return oldmode |
723 return oldmode |
724 |
724 |
725 def init_hooks_mode_categories(self, mode, categories): |
725 def init_hooks_mode_categories(self, mode, categories): |
726 oldmode = self.set_hooks_mode(mode) |
726 oldmode = self.set_hooks_mode(mode) |
727 if mode is self.HOOKS_DENY_ALL: |
727 if mode is self.HOOKS_DENY_ALL: |
728 changes = self.enable_hook_categories(*categories) |
728 changes = self.enable_hook_categories(*categories) |
729 else: |
729 else: |
730 changes = self.disable_hook_categories(*categories) |
730 changes = self.disable_hook_categories(*categories) |
731 self._threaddata.ctx_count += 1 |
731 self._tx.ctx_count += 1 |
732 return oldmode, changes |
732 return oldmode, changes |
733 |
733 |
734 def reset_hooks_mode_categories(self, oldmode, mode, categories): |
734 def reset_hooks_mode_categories(self, oldmode, mode, categories): |
735 txstore = self._threaddata |
735 txstore = self._tx |
736 txstore.ctx_count -= 1 |
736 txstore.ctx_count -= 1 |
737 if txstore.ctx_count == 0: |
737 if txstore.ctx_count == 0: |
738 self._clear_thread_storage(txstore) |
738 self._clear_thread_storage(txstore) |
739 else: |
739 else: |
740 try: |
740 try: |
746 finally: |
746 finally: |
747 self.set_hooks_mode(oldmode) |
747 self.set_hooks_mode(oldmode) |
748 |
748 |
749 @property |
749 @property |
750 def disabled_hook_categories(self): |
750 def disabled_hook_categories(self): |
751 return self._threaddata.disabled_hook_cats |
751 return self._tx.disabled_hook_cats |
752 |
752 |
753 @property |
753 @property |
754 def enabled_hook_categories(self): |
754 def enabled_hook_categories(self): |
755 return self._threaddata.enabled_hook_cats |
755 return self._tx.enabled_hook_cats |
756 |
756 |
757 def disable_hook_categories(self, *categories): |
757 def disable_hook_categories(self, *categories): |
758 """disable the given hook categories: |
758 """disable the given hook categories: |
759 |
759 |
760 - on HOOKS_DENY_ALL mode, ensure those categories are not enabled |
760 - on HOOKS_DENY_ALL mode, ensure those categories are not enabled |
833 self.default_mode = 'transaction' |
833 self.default_mode = 'transaction' |
834 else: # mode == 'write' |
834 else: # mode == 'write' |
835 self.default_mode = 'read' |
835 self.default_mode = 'read' |
836 |
836 |
837 def get_mode(self): |
837 def get_mode(self): |
838 return self._threaddata.mode |
838 return self._tx.mode |
839 def set_mode(self, value): |
839 def set_mode(self, value): |
840 self._threaddata.mode = value |
840 self._tx.mode = value |
841 mode = property(get_mode, set_mode, |
841 mode = property(get_mode, set_mode, |
842 doc='transaction mode (read/write/transaction), resetted to' |
842 doc='transaction mode (read/write/transaction), resetted to' |
843 ' default_mode on commit / rollback') |
843 ' default_mode on commit / rollback') |
844 |
844 |
845 def get_commit_state(self): |
845 def get_commit_state(self): |
846 return self._threaddata.commit_state |
846 return self._tx.commit_state |
847 def set_commit_state(self, value): |
847 def set_commit_state(self, value): |
848 self._threaddata.commit_state = value |
848 self._tx.commit_state = value |
849 commit_state = property(get_commit_state, set_commit_state) |
849 commit_state = property(get_commit_state, set_commit_state) |
850 |
850 |
851 @property |
851 @property |
852 def cnxset(self): |
852 def cnxset(self): |
853 """connections set, set according to transaction mode for each query""" |
853 """connections set, set according to transaction mode for each query""" |
854 if self._closed: |
854 if self._closed: |
855 self.free_cnxset(True) |
855 self.free_cnxset(True) |
856 raise Exception('try to access connections set on a closed session %s' % self.id) |
856 raise Exception('try to access connections set on a closed session %s' % self.id) |
857 return getattr(self._threaddata, 'cnxset', None) |
857 return getattr(self._tx, 'cnxset', None) |
858 |
858 |
859 def set_cnxset(self): |
859 def set_cnxset(self): |
860 """the session need a connections set to execute some queries""" |
860 """the session need a connections set to execute some queries""" |
861 with self._closed_lock: |
861 with self._closed_lock: |
862 if self._closed: |
862 if self._closed: |
863 self.free_cnxset(True) |
863 self.free_cnxset(True) |
864 raise Exception('try to set connections set on a closed session %s' % self.id) |
864 raise Exception('try to set connections set on a closed session %s' % self.id) |
865 if self.cnxset is None: |
865 if self.cnxset is None: |
866 # get connections set first to avoid race-condition |
866 # get connections set first to avoid race-condition |
867 self._threaddata.cnxset = cnxset = self.repo._get_cnxset() |
867 self._tx.cnxset = cnxset = self.repo._get_cnxset() |
868 self._threaddata.ctx_count += 1 |
868 self._tx.ctx_count += 1 |
869 try: |
869 try: |
870 cnxset.cnxset_set() |
870 cnxset.cnxset_set() |
871 except Exception: |
871 except Exception: |
872 self._threaddata.cnxset = None |
872 self._tx.cnxset = None |
873 self.repo._free_cnxset(cnxset) |
873 self.repo._free_cnxset(cnxset) |
874 raise |
874 raise |
875 self._threads_in_transaction.add( |
875 self._threads_in_transaction.add( |
876 (threading.currentThread(), cnxset) ) |
876 (threading.currentThread(), cnxset) ) |
877 return self._threaddata.cnxset |
877 return self._tx.cnxset |
878 |
878 |
879 def _free_thread_cnxset(self, thread, cnxset, force_close=False): |
879 def _free_thread_cnxset(self, thread, cnxset, force_close=False): |
880 try: |
880 try: |
881 self._threads_in_transaction.remove( (thread, cnxset) ) |
881 self._threads_in_transaction.remove( (thread, cnxset) ) |
882 except KeyError: |
882 except KeyError: |
893 |
893 |
894 def free_cnxset(self, ignoremode=False): |
894 def free_cnxset(self, ignoremode=False): |
895 """the session is no longer using its connections set, at least for some time""" |
895 """the session is no longer using its connections set, at least for some time""" |
896 # cnxset may be none if no operation has been done since last commit |
896 # cnxset may be none if no operation has been done since last commit |
897 # or rollback |
897 # or rollback |
898 cnxset = getattr(self._threaddata, 'cnxset', None) |
898 cnxset = getattr(self._tx, 'cnxset', None) |
899 if cnxset is not None and (ignoremode or self.mode == 'read'): |
899 if cnxset is not None and (ignoremode or self.mode == 'read'): |
900 # even in read mode, we must release the current transaction |
900 # even in read mode, we must release the current transaction |
901 self._free_thread_cnxset(threading.currentThread(), cnxset) |
901 self._free_thread_cnxset(threading.currentThread(), cnxset) |
902 del self._threaddata.cnxset |
902 del self._tx.cnxset |
903 self._threaddata.ctx_count -= 1 |
903 self._tx.ctx_count -= 1 |
904 |
904 |
905 def _touch(self): |
905 def _touch(self): |
906 """update latest session usage timestamp and reset mode to read""" |
906 """update latest session usage timestamp and reset mode to read""" |
907 self.timestamp = time() |
907 self.timestamp = time() |
908 self.local_perm_cache.clear() # XXX simply move in transaction_data, no? |
908 self.local_perm_cache.clear() # XXX simply move in transaction_data, no? |
1122 if reset_pool is not None: |
1122 if reset_pool is not None: |
1123 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1123 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1124 DeprecationWarning, stacklevel=2) |
1124 DeprecationWarning, stacklevel=2) |
1125 free_cnxset = reset_pool |
1125 free_cnxset = reset_pool |
1126 # don't use self.cnxset, rollback may be called with _closed == True |
1126 # don't use self.cnxset, rollback may be called with _closed == True |
1127 cnxset = getattr(self._threaddata, 'cnxset', None) |
1127 cnxset = getattr(self._tx, 'cnxset', None) |
1128 if cnxset is None: |
1128 if cnxset is None: |
1129 self._clear_thread_data() |
1129 self._clear_thread_data() |
1130 self._touch() |
1130 self._touch() |
1131 self.debug('rollback session %s done (no db activity)', self.id) |
1131 self.debug('rollback session %s done (no db activity)', self.id) |
1132 return |
1132 return |
1179 |
1179 |
1180 # transaction data/operations management ################################## |
1180 # transaction data/operations management ################################## |
1181 |
1181 |
1182 @property |
1182 @property |
1183 def transaction_data(self): |
1183 def transaction_data(self): |
1184 return self._threaddata.transaction_data |
1184 return self._tx.transaction_data |
1185 |
1185 |
1186 @property |
1186 @property |
1187 def pending_operations(self): |
1187 def pending_operations(self): |
1188 return self._threaddata.pending_operations |
1188 return self._tx.pending_operations |
1189 |
1189 |
1190 @property |
1190 @property |
1191 def pruned_hooks_cache(self): |
1191 def pruned_hooks_cache(self): |
1192 return self._threaddata.pruned_hooks_cache |
1192 return self._tx.pruned_hooks_cache |
1193 |
1193 |
1194 def add_operation(self, operation, index=None): |
1194 def add_operation(self, operation, index=None): |
1195 """add an operation""" |
1195 """add an operation""" |
1196 if index is None: |
1196 if index is None: |
1197 self.pending_operations.append(operation) |
1197 self.pending_operations.append(operation) |
1221 # querier helpers ######################################################### |
1221 # querier helpers ######################################################### |
1222 |
1222 |
1223 @property |
1223 @property |
1224 def rql_rewriter(self): |
1224 def rql_rewriter(self): |
1225 # in thread local storage since the rewriter isn't thread safe |
1225 # in thread local storage since the rewriter isn't thread safe |
1226 return self._threaddata._rewriter |
1226 return self._tx._rewriter |
1227 |
1227 |
1228 # deprecated ############################################################### |
1228 # deprecated ############################################################### |
1229 |
1229 |
1230 @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)') |
1230 @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)') |
1231 def schema_rproperty(self, rtype, eidfrom, eidto, rprop): |
1231 def schema_rproperty(self, rtype, eidfrom, eidto, rprop): |
1298 def cnxset(self): |
1298 def cnxset(self): |
1299 """connections set, set according to transaction mode for each query""" |
1299 """connections set, set according to transaction mode for each query""" |
1300 if self.repo.shutting_down: |
1300 if self.repo.shutting_down: |
1301 self.free_cnxset(True) |
1301 self.free_cnxset(True) |
1302 raise ShuttingDown('repository is shutting down') |
1302 raise ShuttingDown('repository is shutting down') |
1303 return getattr(self._threaddata, 'cnxset', None) |
1303 return getattr(self._tx, 'cnxset', None) |
1304 |
1304 |
1305 |
1305 |
1306 class InternalManager(object): |
1306 class InternalManager(object): |
1307 """a manager user with all access rights used internally for task such as |
1307 """a manager user with all access rights used internally for task such as |
1308 bootstrapping the repository or creating regular users according to |
1308 bootstrapping the repository or creating regular users according to |