server/session.py
branchstable
changeset 5813 0b250d72fcfa
parent 5802 159b6a712d9d
child 5815 282194aa43f3
child 5826 462435bf5457
equal deleted inserted replaced
5812:d970049d7cfd 5813:0b250d72fcfa
   115         if self.write is not None:
   115         if self.write is not None:
   116             self.session.set_write_security(self.oldwrite)
   116             self.session.set_write_security(self.oldwrite)
   117 #            print INDENT + 'reset write to', self.oldwrite
   117 #            print INDENT + 'reset write to', self.oldwrite
   118 
   118 
   119 
   119 
       
   120 class TransactionData(object):
       
   121     def __init__(self, txid):
       
   122         self.transactionid = txid
   120 
   123 
   121 class Session(RequestSessionBase):
   124 class Session(RequestSessionBase):
   122     """tie session id, user, connections pool and other session data all
   125     """tie session id, user, connections pool and other session data all
   123     together
   126     together
   124     """
   127     """
   146         # and the rql server
   149         # and the rql server
   147         self.data = {}
   150         self.data = {}
   148         # i18n initialization
   151         # i18n initialization
   149         self.set_language(cnxprops.lang)
   152         self.set_language(cnxprops.lang)
   150         # internals
   153         # internals
   151         self._threaddata = threading.local()
   154         self._tx_data = {}
       
   155         self.__threaddata = threading.local()
   152         self._threads_in_transaction = set()
   156         self._threads_in_transaction = set()
   153         self._closed = False
   157         self._closed = False
   154 
   158 
   155     def __unicode__(self):
   159     def __unicode__(self):
   156         return '<%ssession %s (%s 0x%x)>' % (
   160         return '<%ssession %s (%s 0x%x)>' % (
   157             self.cnxtype, unicode(self.user.login), self.id, id(self))
   161             self.cnxtype, unicode(self.user.login), self.id, id(self))
       
   162 
       
   163     def set_tx_data(self, txid=None):
       
   164         if txid is None:
       
   165             txid = threading.currentThread().getName()
       
   166         try:
       
   167             self.__threaddata.txdata = self._tx_data[txid]
       
   168         except KeyError:
       
   169             self.__threaddata.txdata = self._tx_data[txid] = TransactionData(txid)
       
   170 
       
   171     @property
       
   172     def _threaddata(self):
       
   173         try:
       
   174             return self.__threaddata.txdata
       
   175         except AttributeError:
       
   176             self.set_tx_data()
       
   177             return self.__threaddata.txdata
       
   178 
   158 
   179 
   159     def hijack_user(self, user):
   180     def hijack_user(self, user):
   160         """return a fake request/session using specified user"""
   181         """return a fake request/session using specified user"""
   161         session = Session(user, self.repo)
   182         session = Session(user, self.repo)
   162         threaddata = session._threaddata
   183         threaddata = session._threaddata
   336     DEFAULT_SECURITY = object() # evaluated to true by design
   357     DEFAULT_SECURITY = object() # evaluated to true by design
   337 
   358 
   338     @property
   359     @property
   339     def read_security(self):
   360     def read_security(self):
   340         """return a boolean telling if read security is activated or not"""
   361         """return a boolean telling if read security is activated or not"""
   341         try:
   362         txstore = self._threaddata
   342             return self._threaddata.read_security
   363         if txstore is None:
       
   364             return self.DEFAULT_SECURITY
       
   365         try:
       
   366             return txstore.read_security
   343         except AttributeError:
   367         except AttributeError:
   344             self._threaddata.read_security = self.DEFAULT_SECURITY
   368             txstore.read_security = self.DEFAULT_SECURITY
   345             return self._threaddata.read_security
   369             return txstore.read_security
   346 
   370 
   347     def set_read_security(self, activated):
   371     def set_read_security(self, activated):
   348         """[de]activate read security, returning the previous value set for
   372         """[de]activate read security, returning the previous value set for
   349         later restoration.
   373         later restoration.
   350 
   374 
   351         you should usually use the `security_enabled` context manager instead
   375         you should usually use the `security_enabled` context manager instead
   352         of this to change security settings.
   376         of this to change security settings.
   353         """
   377         """
   354         oldmode = self.read_security
   378         txstore = self._threaddata
   355         self._threaddata.read_security = activated
   379         if txstore is None:
       
   380             return self.DEFAULT_SECURITY
       
   381         oldmode = getattr(txstore, 'read_security', self.DEFAULT_SECURITY)
       
   382         txstore.read_security = activated
   356         # dbapi_query used to detect hooks triggered by a 'dbapi' query (eg not
   383         # dbapi_query used to detect hooks triggered by a 'dbapi' query (eg not
   357         # issued on the session). This is tricky since we the execution model of
   384         # issued on the session). This is tricky since we the execution model of
   358         # a (write) user query is:
   385         # a (write) user query is:
   359         #
   386         #
   360         # repository.execute (security enabled)
   387         # repository.execute (security enabled)
   367         # so we can't rely on simply checking session.read_security, but
   394         # so we can't rely on simply checking session.read_security, but
   368         # recalling the first transition from DEFAULT_SECURITY to something
   395         # recalling the first transition from DEFAULT_SECURITY to something
   369         # else (False actually) is not perfect but should be enough
   396         # else (False actually) is not perfect but should be enough
   370         #
   397         #
   371         # also reset dbapi_query to true when we go back to DEFAULT_SECURITY
   398         # also reset dbapi_query to true when we go back to DEFAULT_SECURITY
   372         self._threaddata.dbapi_query = (oldmode is self.DEFAULT_SECURITY
   399         txstore.dbapi_query = (oldmode is self.DEFAULT_SECURITY
   373                                         or activated is self.DEFAULT_SECURITY)
   400                                or activated is self.DEFAULT_SECURITY)
   374         return oldmode
   401         return oldmode
   375 
   402 
   376     @property
   403     @property
   377     def write_security(self):
   404     def write_security(self):
   378         """return a boolean telling if write security is activated or not"""
   405         """return a boolean telling if write security is activated or not"""
   379         try:
   406         txstore = self._threaddata
   380             return self._threaddata.write_security
   407         if txstore is None:
       
   408             return self.DEFAULT_SECURITY
       
   409         try:
       
   410             return txstore.write_security
   381         except:
   411         except:
   382             self._threaddata.write_security = self.DEFAULT_SECURITY
   412             txstore.write_security = self.DEFAULT_SECURITY
   383             return self._threaddata.write_security
   413             return txstore.write_security
   384 
   414 
   385     def set_write_security(self, activated):
   415     def set_write_security(self, activated):
   386         """[de]activate write security, returning the previous value set for
   416         """[de]activate write security, returning the previous value set for
   387         later restoration.
   417         later restoration.
   388 
   418 
   389         you should usually use the `security_enabled` context manager instead
   419         you should usually use the `security_enabled` context manager instead
   390         of this to change security settings.
   420         of this to change security settings.
   391         """
   421         """
   392         oldmode = self.write_security
   422         txstore = self._threaddata
   393         self._threaddata.write_security = activated
   423         if txstore is None:
       
   424             return self.DEFAULT_SECURITY
       
   425         oldmode = getattr(txstore, 'write_security', self.DEFAULT_SECURITY)
       
   426         txstore.write_security = activated
   394         return oldmode
   427         return oldmode
   395 
   428 
   396     @property
   429     @property
   397     def running_dbapi_query(self):
   430     def running_dbapi_query(self):
   398         """return a boolean telling if it's triggered by a db-api query or by
   431         """return a boolean telling if it's triggered by a db-api query or by
   565 
   598 
   566     def _touch(self):
   599     def _touch(self):
   567         """update latest session usage timestamp and reset mode to read"""
   600         """update latest session usage timestamp and reset mode to read"""
   568         self.timestamp = time()
   601         self.timestamp = time()
   569         self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
   602         self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
   570         self._threaddata.mode = self.default_mode
       
   571 
   603 
   572     # shared data handling ###################################################
   604     # shared data handling ###################################################
   573 
   605 
   574     def get_shared_data(self, key, default=None, pop=False):
   606     def get_shared_data(self, key, default=None, pop=False):
   575         """return value associated to `key` in session data"""
   607         """return value associated to `key` in session data"""
   655         self.timestamp = time() # update timestamp
   687         self.timestamp = time() # update timestamp
   656         rset = self._execute(self, rql, kwargs, build_descr)
   688         rset = self._execute(self, rql, kwargs, build_descr)
   657         rset.req = self
   689         rset.req = self
   658         return rset
   690         return rset
   659 
   691 
   660     def _clear_thread_data(self):
   692     def _clear_thread_data(self, reset_pool=True):
   661         """remove everything from the thread local storage, except pool
   693         """remove everything from the thread local storage, except pool
   662         which is explicitly removed by reset_pool, and mode which is set anyway
   694         which is explicitly removed by reset_pool, and mode which is set anyway
   663         by _touch
   695         by _touch
   664         """
   696         """
   665         store = self._threaddata
   697         try:
   666         for name in ('commit_state', 'transaction_data', 'pending_operations',
   698             txstore = self.__threaddata.txdata
   667                      '_rewriter'):
   699         except AttributeError:
   668             try:
   700             pass
   669                 delattr(store, name)
   701         else:
   670             except AttributeError:
   702             if reset_pool:
   671                 pass
   703                 self._tx_data.pop(txstore.transactionid, None)
       
   704                 try:
       
   705                     del self.__threaddata.txdata
       
   706                 except AttributeError:
       
   707                     pass
       
   708             else:
       
   709                 for name in ('commit_state', 'transaction_data',
       
   710                              'pending_operations', '_rewriter'):
       
   711                     try:
       
   712                         delattr(txstore, name)
       
   713                     except AttributeError:
       
   714                         continue
   672 
   715 
   673     def commit(self, reset_pool=True):
   716     def commit(self, reset_pool=True):
   674         """commit the current session's transaction"""
   717         """commit the current session's transaction"""
   675         if self.pool is None:
   718         if self.pool is None:
   676             assert not self.pending_operations
   719             assert not self.pending_operations
   678             self._touch()
   721             self._touch()
   679             self.debug('commit session %s done (no db activity)', self.id)
   722             self.debug('commit session %s done (no db activity)', self.id)
   680             return
   723             return
   681         if self.commit_state:
   724         if self.commit_state:
   682             return
   725             return
   683         # by default, operations are executed with security turned off
   726         # on rollback, an operation should have the following state
   684         with security_enabled(self, False, False):
   727         # information:
   685             # on rollback, an operation should have the following state
   728         # - processed by the precommit/commit event or not
   686             # information:
   729         # - if processed, is it the failed operation
   687             # - processed by the precommit/commit event or not
   730         try:
   688             # - if processed, is it the failed operation
   731             # by default, operations are executed with security turned off
   689             try:
   732             with security_enabled(self, False, False):
   690                 for trstate in ('precommit', 'commit'):
   733                 for trstate in ('precommit', 'commit'):
   691                     processed = []
   734                     processed = []
   692                     self.commit_state = trstate
   735                     self.commit_state = trstate
   693                     try:
   736                     try:
   694                         while self.pending_operations:
   737                         while self.pending_operations:
   728                     except:
   771                     except:
   729                         self.critical('error while %sing', trstate,
   772                         self.critical('error while %sing', trstate,
   730                                       exc_info=sys.exc_info())
   773                                       exc_info=sys.exc_info())
   731                 self.info('%s session %s done', trstate, self.id)
   774                 self.info('%s session %s done', trstate, self.id)
   732                 return self.transaction_uuid(set=False)
   775                 return self.transaction_uuid(set=False)
   733             finally:
   776         finally:
   734                 self._clear_thread_data()
   777             self._touch()
   735                 self._touch()
   778             if reset_pool:
   736                 if reset_pool:
   779                 self.reset_pool(ignoremode=True)
   737                     self.reset_pool(ignoremode=True)
   780             self._clear_thread_data(reset_pool)
   738 
   781 
   739     def rollback(self, reset_pool=True):
   782     def rollback(self, reset_pool=True):
   740         """rollback the current session's transaction"""
   783         """rollback the current session's transaction"""
   741         if self.pool is None:
   784         if self.pool is None:
   742             assert not self.pending_operations
       
   743             self._clear_thread_data()
   785             self._clear_thread_data()
   744             self._touch()
   786             self._touch()
   745             self.debug('rollback session %s done (no db activity)', self.id)
   787             self.debug('rollback session %s done (no db activity)', self.id)
   746             return
   788             return
   747         # by default, operations are executed with security turned off
   789         try:
   748         with security_enabled(self, False, False):
   790             # by default, operations are executed with security turned off
   749             try:
   791             with security_enabled(self, False, False):
   750                 while self.pending_operations:
   792                 while self.pending_operations:
   751                     try:
   793                     try:
   752                         operation = self.pending_operations.pop(0)
   794                         operation = self.pending_operations.pop(0)
   753                         operation.handle_event('rollback_event')
   795                         operation.handle_event('rollback_event')
   754                     except:
   796                     except:
   755                         self.critical('rollback error', exc_info=sys.exc_info())
   797                         self.critical('rollback error', exc_info=sys.exc_info())
   756                         continue
   798                         continue
   757                 self.pool.rollback()
   799                 self.pool.rollback()
   758                 self.debug('rollback for session %s done', self.id)
   800                 self.debug('rollback for session %s done', self.id)
   759             finally:
   801         finally:
   760                 self._clear_thread_data()
   802             self._touch()
   761                 self._touch()
   803             if reset_pool:
   762                 if reset_pool:
   804                 self.reset_pool(ignoremode=True)
   763                     self.reset_pool(ignoremode=True)
   805             self._clear_thread_data(reset_pool)
   764 
   806 
   765     def close(self):
   807     def close(self):
   766         """do not close pool on session close, since they are shared now"""
   808         """do not close pool on session close, since they are shared now"""
   767         self._closed = True
   809         self._closed = True
   768         # copy since _threads_in_transaction maybe modified while waiting
   810         # copy since _threads_in_transaction maybe modified while waiting
   780                     break
   822                     break
   781             else:
   823             else:
   782                 self.error('thread %s still alive after 10 seconds, will close '
   824                 self.error('thread %s still alive after 10 seconds, will close '
   783                            'session anyway', thread)
   825                            'session anyway', thread)
   784         self.rollback()
   826         self.rollback()
   785         del self._threaddata
   827         del self.__threaddata
       
   828         del self._tx_data
   786 
   829 
   787     # transaction data/operations management ##################################
   830     # transaction data/operations management ##################################
   788 
   831 
   789     @property
   832     @property
   790     def transaction_data(self):
   833     def transaction_data(self):