--- a/server/session.py Mon Sep 26 18:37:23 2011 +0200
+++ b/server/session.py Fri Dec 09 12:08:27 2011 +0100
@@ -61,6 +61,18 @@
description.append(term.get_type(solution, args))
return description
+def selection_idx_type(i, rqlst, args):
+ """try to return type of term at index `i` of the rqlst's selection"""
+ for select in rqlst.children:
+ term = select.selection[i]
+ for solution in select.solutions:
+ try:
+ ttype = term.get_type(solution, args)
+ if ttype is not None:
+ return ttype
+ except CoercionError:
+ return None
+
@objectify_selector
def is_user_session(cls, req, **kwargs):
"""repository side only selector returning 1 if the session is a regular
@@ -125,21 +137,13 @@
self.categories = categories
def __enter__(self):
- self.oldmode = self.session.set_hooks_mode(self.mode)
- if self.mode is self.session.HOOKS_DENY_ALL:
- self.changes = self.session.enable_hook_categories(*self.categories)
- else:
- self.changes = self.session.disable_hook_categories(*self.categories)
+ self.oldmode, self.changes = self.session.init_hooks_mode_categories(
+ self.mode, self.categories)
def __exit__(self, exctype, exc, traceback):
- if self.changes:
- if self.mode is self.session.HOOKS_DENY_ALL:
- self.session.disable_hook_categories(*self.changes)
- else:
- self.session.enable_hook_categories(*self.changes)
- self.session.set_hooks_mode(self.oldmode)
+ self.session.reset_hooks_mode_categories(self.oldmode, self.mode, self.changes)
-INDENT = ''
+
class security_enabled(object):
"""context manager to control security w/ session.execute, since by
default security is disabled on queries executed on the repository
@@ -151,33 +155,90 @@
self.write = write
def __enter__(self):
-# global INDENT
- if self.read is not None:
- self.oldread = self.session.set_read_security(self.read)
-# print INDENT + 'read', self.read, self.oldread
- if self.write is not None:
- self.oldwrite = self.session.set_write_security(self.write)
-# print INDENT + 'write', self.write, self.oldwrite
-# INDENT += ' '
+ self.oldread, self.oldwrite = self.session.init_security(
+ self.read, self.write)
def __exit__(self, exctype, exc, traceback):
-# global INDENT
-# INDENT = INDENT[:-2]
- if self.read is not None:
- self.session.set_read_security(self.oldread)
-# print INDENT + 'reset read to', self.oldread
- if self.write is not None:
- self.session.set_write_security(self.oldwrite)
-# print INDENT + 'reset write to', self.oldwrite
+ self.session.reset_security(self.oldread, self.oldwrite)
class TransactionData(object):
def __init__(self, txid):
self.transactionid = txid
+ self.ctx_count = 0
+
class Session(RequestSessionBase):
- """tie session id, user, connections pool and other session data all
- together
+ """Repository usersession, tie a session id, user, connections set and
+ other session data all together.
+
+ About session storage / transactions
+ ------------------------------------
+
+ Here is a description of internal session attributes. Besides :attr:`data`
+ and :attr:`transaction_data`, you should not have to use attributes
+ described here but higher level APIs.
+
+ :attr:`data` is a dictionary containing shared data, used to communicate
+ extra information between the client and the repository
+
+ :attr:`_tx_data` is a dictionary of :class:`TransactionData` instance, one
+ for each running transaction. The key is the transaction id. By default
+ the transaction id is the thread name but it can be otherwise (per dbapi
+ cursor for instance, or per thread name *from another process*).
+
+ :attr:`__threaddata` is a thread local storage whose `txdata` attribute
+ refers to the proper instance of :class:`TransactionData` according to the
+ transaction.
+
+ :attr:`_threads_in_transaction` is a set of (thread, connections set)
+ referencing threads that currently hold a connections set for the session.
+
+ You should not have to use neither :attr:`_txdata` nor :attr:`__threaddata`,
+ simply access transaction data transparently through the :attr:`_threaddata`
+ property. Also, you usually don't have to access it directly since current
+ transaction's data may be accessed/modified through properties / methods:
+
+ :attr:`transaction_data`, similarly to :attr:`data`, is a dictionary
+ containing some shared data that should be cleared at the end of the
+ transaction. Hooks and operations may put arbitrary data in there, and
+ this may also be used as a communication channel between the client and
+ the repository.
+
+ :attr:`cnxset`, the connections set to use to execute queries on sources.
+ During a transaction, the connection set may be freed so that is may be
+ used by another session as long as no writing is done. This means we can
+ have multiple sessions with a reasonably low connections set pool size.
+
+ :attr:`mode`, string telling the connections set handling mode, may be one
+ of 'read' (connections set may be freed), 'write' (some write was done in
+ the connections set, it can't be freed before end of the transaction),
+ 'transaction' (we want to keep the connections set during all the
+ transaction, with or without writing)
+
+ :attr:`pending_operations`, ordered list of operations to be processed on
+ commit/rollback
+
+ :attr:`commit_state`, describing the transaction commit state, may be one
+ of None (not yet committing), 'precommit' (calling precommit event on
+ operations), 'postcommit' (calling postcommit event on operations),
+ 'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error
+ has been raised during the transaction and so it must be rollbacked).
+
+ :attr:`read_security` and :attr:`write_security`, boolean flags telling if
+ read/write security is currently activated.
+
+ :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`.
+
+ :attr:`enabled_hook_categories`, when :attr:`hooks_mode` is
+ `HOOKS_DENY_ALL`, this set contains hooks categories that are enabled.
+
+ :attr:`disabled_hook_categories`, when :attr:`hooks_mode` is
+ `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled.
+
+
+ :attr:`running_dbapi_query`, boolean flag telling if the executing query
+ is coming from a dbapi connection or is a query from within the repository
"""
is_internal_session = False
@@ -246,7 +307,10 @@
"""return a fake request/session using specified user"""
session = Session(user, self.repo)
threaddata = session._threaddata
- threaddata.pool = self.pool
+ threaddata.cnxset = self.cnxset
+ # we attributed a connections set, need to update ctx_count else it will be freed
+ # while undesired
+ threaddata.ctx_count = 1
# share pending_operations, else operation added in the hi-jacked
# session such as SendMailOp won't ever be processed
threaddata.pending_operations = self.pending_operations
@@ -388,14 +452,14 @@
"""return a sql cursor on the system database"""
if sql.split(None, 1)[0].upper() != 'SELECT':
self.mode = 'write'
- source = self.pool.source('system')
+ source = self.cnxset.source('system')
try:
return source.doexec(self, sql, args, rollback=rollback_on_failure)
except (source.OperationalError, source.InterfaceError):
if not rollback_on_failure:
raise
source.warning("trying to reconnect")
- self.pool.reconnect(source)
+ self.cnxset.reconnect(source)
return source.doexec(self, sql, args, rollback=rollback_on_failure)
def set_language(self, language):
@@ -446,6 +510,29 @@
def security_enabled(self, read=False, write=False):
return security_enabled(self, read=read, write=write)
+ def init_security(self, read, write):
+ if read is None:
+ oldread = None
+ else:
+ oldread = self.set_read_security(read)
+ if write is None:
+ oldwrite = None
+ else:
+ oldwrite = self.set_write_security(write)
+ self._threaddata.ctx_count += 1
+ return oldread, oldwrite
+
+ def reset_security(self, read, write):
+ txstore = self._threaddata
+ txstore.ctx_count -= 1
+ if txstore.ctx_count == 0:
+ self._clear_thread_storage(txstore)
+ else:
+ if read is not None:
+ self.set_read_security(read)
+ if write is not None:
+ self.set_write_security(write)
+
@property
def read_security(self):
"""return a boolean telling if read security is activated or not"""
@@ -498,7 +585,7 @@
return self.DEFAULT_SECURITY
try:
return txstore.write_security
- except:
+ except AttributeError:
txstore.write_security = self.DEFAULT_SECURITY
return txstore.write_security
@@ -546,6 +633,30 @@
self._threaddata.hooks_mode = mode
return oldmode
+ def init_hooks_mode_categories(self, mode, categories):
+ oldmode = self.set_hooks_mode(mode)
+ if mode is self.HOOKS_DENY_ALL:
+ changes = self.enable_hook_categories(*categories)
+ else:
+ changes = self.disable_hook_categories(*categories)
+ self._threaddata.ctx_count += 1
+ return oldmode, changes
+
+ def reset_hooks_mode_categories(self, oldmode, mode, categories):
+ txstore = self._threaddata
+ txstore.ctx_count -= 1
+ if txstore.ctx_count == 0:
+ self._clear_thread_storage(txstore)
+ else:
+ try:
+ if categories:
+ if mode is self.HOOKS_DENY_ALL:
+ return self.disable_hook_categories(*categories)
+ else:
+ return self.enable_hook_categories(*categories)
+ finally:
+ self.set_hooks_mode(oldmode)
+
@property
def disabled_hook_categories(self):
try:
@@ -569,17 +680,18 @@
- on HOOKS_ALLOW_ALL mode, ensure those categories are disabled
"""
changes = set()
+ self.pruned_hooks_cache.clear()
if self.hooks_mode is self.HOOKS_DENY_ALL:
- enablecats = self.enabled_hook_categories
+ enabledcats = self.enabled_hook_categories
for category in categories:
- if category in enablecats:
- enablecats.remove(category)
+ if category in enabledcats:
+ enabledcats.remove(category)
changes.add(category)
else:
- disablecats = self.disabled_hook_categories
+ disabledcats = self.disabled_hook_categories
for category in categories:
- if category not in disablecats:
- disablecats.add(category)
+ if category not in disabledcats:
+ disabledcats.add(category)
changes.add(category)
return tuple(changes)
@@ -590,17 +702,18 @@
- on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled
"""
changes = set()
+ self.pruned_hooks_cache.clear()
if self.hooks_mode is self.HOOKS_DENY_ALL:
- enablecats = self.enabled_hook_categories
+ enabledcats = self.enabled_hook_categories
for category in categories:
- if category not in enablecats:
- enablecats.add(category)
+ if category not in enabledcats:
+ enabledcats.add(category)
changes.add(category)
else:
- disablecats = self.disabled_hook_categories
+ disabledcats = self.disabled_hook_categories
for category in categories:
- if category in self.disabled_hook_categories:
- disablecats.remove(category)
+ if category in disabledcats:
+ disabledcats.remove(category)
changes.add(category)
return tuple(changes)
@@ -620,19 +733,19 @@
# connection management ###################################################
- def keep_pool_mode(self, mode):
- """set pool_mode, e.g. how the session will keep its pool:
+ def keep_cnxset_mode(self, mode):
+ """set `mode`, e.g. how the session will keep its connections set:
- * if mode == 'write', the pool is freed after each ready query, but kept
- until the transaction's end (eg commit or rollback) when a write query
- is detected (eg INSERT/SET/DELETE queries)
+ * if mode == 'write', the connections set is freed after each ready
+ query, but kept until the transaction's end (eg commit or rollback)
+ when a write query is detected (eg INSERT/SET/DELETE queries)
- * if mode == 'transaction', the pool is only freed after the
+ * if mode == 'transaction', the connections set is only freed after the
transaction's end
- notice that a repository has a limited set of pools, and a session has to
- wait for a free pool to run any rql query (unless it already has a pool
- set).
+ notice that a repository has a limited set of connections sets, and a
+ session has to wait for a free connections set to run any rql query
+ (unless it already has one set).
"""
assert mode in ('transaction', 'write')
if mode == 'transaction':
@@ -655,56 +768,58 @@
commit_state = property(get_commit_state, set_commit_state)
@property
- def pool(self):
- """connections pool, set according to transaction mode for each query"""
+ def cnxset(self):
+ """connections set, set according to transaction mode for each query"""
if self._closed:
- self.reset_pool(True)
- raise Exception('try to access pool on a closed session')
- return getattr(self._threaddata, 'pool', None)
+ self.free_cnxset(True)
+ raise Exception('try to access connections set on a closed session %s' % self.id)
+ return getattr(self._threaddata, 'cnxset', None)
- def set_pool(self):
- """the session need a pool to execute some queries"""
+ def set_cnxset(self):
+ """the session need a connections set to execute some queries"""
with self._closed_lock:
if self._closed:
- self.reset_pool(True)
- raise Exception('try to set pool on a closed session')
- if self.pool is None:
- # get pool first to avoid race-condition
- self._threaddata.pool = pool = self.repo._get_pool()
+ self.free_cnxset(True)
+ raise Exception('try to set connections set on a closed session %s' % self.id)
+ if self.cnxset is None:
+ # get connections set first to avoid race-condition
+ self._threaddata.cnxset = cnxset = self.repo._get_cnxset()
+ self._threaddata.ctx_count += 1
try:
- pool.pool_set()
- except:
- self._threaddata.pool = None
- self.repo._free_pool(pool)
+ cnxset.cnxset_set()
+ except Exception:
+ self._threaddata.cnxset = None
+ self.repo._free_cnxset(cnxset)
raise
self._threads_in_transaction.add(
- (threading.currentThread(), pool) )
- return self._threaddata.pool
+ (threading.currentThread(), cnxset) )
+ return self._threaddata.cnxset
- def _free_thread_pool(self, thread, pool, force_close=False):
+ def _free_thread_cnxset(self, thread, cnxset, force_close=False):
try:
- self._threads_in_transaction.remove( (thread, pool) )
+ self._threads_in_transaction.remove( (thread, cnxset) )
except KeyError:
- # race condition on pool freeing (freed by commit or rollback vs
+ # race condition on cnxset freeing (freed by commit or rollback vs
# close)
pass
else:
if force_close:
- pool.reconnect()
+ cnxset.reconnect()
else:
- pool.pool_reset()
- # free pool once everything is done to avoid race-condition
- self.repo._free_pool(pool)
+ cnxset.cnxset_freed()
+ # free cnxset once everything is done to avoid race-condition
+ self.repo._free_cnxset(cnxset)
- def reset_pool(self, ignoremode=False):
- """the session is no longer using its pool, at least for some time"""
- # pool may be none if no operation has been done since last commit
+ def free_cnxset(self, ignoremode=False):
+ """the session 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
- pool = getattr(self._threaddata, 'pool', None)
- if pool is not None and (ignoremode or self.mode == 'read'):
+ cnxset = getattr(self._threaddata, 'cnxset', None)
+ if cnxset is not None and (ignoremode or self.mode == 'read'):
# even in read mode, we must release the current transaction
- self._free_thread_pool(threading.currentThread(), pool)
- del self._threaddata.pool
+ self._free_thread_cnxset(threading.currentThread(), cnxset)
+ del self._threaddata.cnxset
+ self._threaddata.ctx_count -= 1
def _touch(self):
"""update latest session usage timestamp and reset mode to read"""
@@ -770,9 +885,13 @@
def source_defs(self):
return self.repo.source_defs()
- def describe(self, eid):
+ def describe(self, eid, asdict=False):
"""return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
- return self.repo.type_and_source_from_eid(eid, self)
+ metas = self.repo.type_and_source_from_eid(eid, self)
+ if asdict:
+ return dict(zip(('type', 'source', 'extid', 'asource'), metas))
+ # XXX :-1 for cw compat, use asdict=True for full information
+ return metas[:-1]
# db-api like interface ###################################################
@@ -793,9 +912,9 @@
rset.req = self
return rset
- def _clear_thread_data(self, reset_pool=True):
- """remove everything from the thread local storage, except pool
- which is explicitly removed by reset_pool, and mode which is set anyway
+ def _clear_thread_data(self, free_cnxset=True):
+ """remove everything from the thread local storage, except connections set
+ which is explicitly removed by free_cnxset, and mode which is set anyway
by _touch
"""
try:
@@ -803,23 +922,38 @@
except AttributeError:
pass
else:
- if reset_pool:
- self._tx_data.pop(txstore.transactionid, None)
- try:
- del self.__threaddata.txdata
- except AttributeError:
- pass
+ if free_cnxset:
+ self.free_cnxset()
+ if txstore.ctx_count == 0:
+ self._clear_thread_storage(txstore)
+ else:
+ self._clear_tx_storage(txstore)
else:
- for name in ('commit_state', 'transaction_data',
- 'pending_operations', '_rewriter'):
- try:
- delattr(txstore, name)
- except AttributeError:
- continue
+ self._clear_tx_storage(txstore)
+
+ def _clear_thread_storage(self, txstore):
+ self._tx_data.pop(txstore.transactionid, None)
+ try:
+ del self.__threaddata.txdata
+ except AttributeError:
+ pass
- def commit(self, reset_pool=True):
+ def _clear_tx_storage(self, txstore):
+ for name in ('commit_state', 'transaction_data',
+ 'pending_operations', '_rewriter',
+ 'pruned_hooks_cache'):
+ try:
+ delattr(txstore, name)
+ except AttributeError:
+ continue
+
+ def commit(self, free_cnxset=True, reset_pool=None):
"""commit the current session's transaction"""
- if self.pool is None:
+ if reset_pool is not None:
+ warn('[3.13] use free_cnxset argument instead for reset_pool',
+ DeprecationWarning, stacklevel=2)
+ free_cnxset = reset_pool
+ if self.cnxset is None:
assert not self.pending_operations
self._clear_thread_data()
self._touch()
@@ -847,7 +981,7 @@
operation.handle_event('precommit_event')
self.pending_operations[:] = processed
self.debug('precommit session %s done', self.id)
- except:
+ except BaseException:
# if error on [pre]commit:
#
# * set .failed = True on the operation causing the failure
@@ -862,37 +996,41 @@
for operation in reversed(processed):
try:
operation.handle_event('revertprecommit_event')
- except:
+ except BaseException:
self.critical('error while reverting precommit',
exc_info=True)
# XXX use slice notation since self.pending_operations is a
# read-only property.
self.pending_operations[:] = processed + self.pending_operations
- self.rollback(reset_pool)
+ self.rollback(free_cnxset)
raise
- self.pool.commit()
+ self.cnxset.commit()
self.commit_state = 'postcommit'
while self.pending_operations:
operation = self.pending_operations.pop(0)
operation.processed = 'postcommit'
try:
operation.handle_event('postcommit_event')
- except:
+ except BaseException:
self.critical('error while postcommit',
exc_info=sys.exc_info())
self.debug('postcommit session %s done', self.id)
return self.transaction_uuid(set=False)
finally:
self._touch()
- if reset_pool:
- self.reset_pool(ignoremode=True)
- self._clear_thread_data(reset_pool)
+ if free_cnxset:
+ self.free_cnxset(ignoremode=True)
+ self._clear_thread_data(free_cnxset)
- def rollback(self, reset_pool=True):
+ def rollback(self, free_cnxset=True, reset_pool=None):
"""rollback the current session's transaction"""
- # don't use self.pool, rollback may be called with _closed == True
- pool = getattr(self._threaddata, 'pool', None)
- if pool is None:
+ if reset_pool is not None:
+ warn('[3.13] use free_cnxset argument instead for reset_pool',
+ DeprecationWarning, stacklevel=2)
+ free_cnxset = reset_pool
+ # don't use self.cnxset, rollback may be called with _closed == True
+ cnxset = getattr(self._threaddata, 'cnxset', None)
+ if cnxset is None:
self._clear_thread_data()
self._touch()
self.debug('rollback session %s done (no db activity)', self.id)
@@ -904,23 +1042,23 @@
try:
operation = self.pending_operations.pop(0)
operation.handle_event('rollback_event')
- except:
+ except BaseException:
self.critical('rollback error', exc_info=sys.exc_info())
continue
- pool.rollback()
+ cnxset.rollback()
self.debug('rollback for session %s done', self.id)
finally:
self._touch()
- if reset_pool:
- self.reset_pool(ignoremode=True)
- self._clear_thread_data(reset_pool)
+ if free_cnxset:
+ self.free_cnxset(ignoremode=True)
+ self._clear_thread_data(free_cnxset)
def close(self):
- """do not close pool on session close, since they are shared now"""
+ """do not close connections set on session close, since they are shared now"""
with self._closed_lock:
self._closed = True
# copy since _threads_in_transaction maybe modified while waiting
- for thread, pool in self._threads_in_transaction.copy():
+ for thread, cnxset in self._threads_in_transaction.copy():
if thread is threading.currentThread():
continue
self.info('waiting for thread %s', thread)
@@ -930,12 +1068,12 @@
for i in xrange(10):
thread.join(1)
if not (thread.isAlive() and
- (thread, pool) in self._threads_in_transaction):
+ (thread, cnxset) in self._threads_in_transaction):
break
else:
self.error('thread %s still alive after 10 seconds, will close '
'session anyway', thread)
- self._free_thread_pool(thread, pool, force_close=True)
+ self._free_thread_cnxset(thread, cnxset, force_close=True)
self.rollback()
del self.__threaddata
del self._tx_data
@@ -962,9 +1100,16 @@
self._threaddata.pending_operations = []
return self._threaddata.pending_operations
+ @property
+ def pruned_hooks_cache(self):
+ try:
+ return self._threaddata.pruned_hooks_cache
+ except AttributeError:
+ self._threaddata.pruned_hooks_cache = {}
+ return self._threaddata.pruned_hooks_cache
+
def add_operation(self, operation, index=None):
- """add an observer"""
- assert self.commit_state != 'commit'
+ """add an operation"""
if index is None:
self.pending_operations.append(operation)
else:
@@ -1023,20 +1168,13 @@
unstables = rqlst.get_variable_indices()
basedescr = []
todetermine = []
- sampleselect = rqlst.children[0]
- samplesols = sampleselect.solutions[0]
- for i, term in enumerate(sampleselect.selection):
- try:
- ttype = term.get_type(samplesols, args)
- except CoercionError:
+ for i in xrange(len(rqlst.children[0].selection)):
+ ttype = selection_idx_type(i, rqlst, args)
+ if ttype is None or ttype == 'Any':
ttype = None
isfinal = True
else:
- if ttype is None or ttype == 'Any':
- ttype = None
- isfinal = True
- else:
- isfinal = ttype in BASE_TYPES
+ isfinal = ttype in BASE_TYPES
if ttype is None or i in unstables:
basedescr.append(None)
todetermine.append( (i, isfinal) )
@@ -1049,7 +1187,8 @@
def _build_descr(self, result, basedescription, todetermine):
description = []
etype_from_eid = self.describe
- for row in result:
+ todel = []
+ for i, row in enumerate(result):
row_descr = basedescription[:]
for index, isfinal in todetermine:
value = row[index]
@@ -1063,10 +1202,14 @@
try:
row_descr[index] = etype_from_eid(value)[0]
except UnknownEid:
- self.critical('wrong eid %s in repository, you should '
- 'db-check the database' % value)
- row_descr[index] = row[index] = None
- description.append(tuple(row_descr))
+ self.error('wrong eid %s in repository, you should '
+ 'db-check the database' % value)
+ todel.append(i)
+ break
+ else:
+ description.append(tuple(row_descr))
+ for i in reversed(todel):
+ del result[i]
return description
# deprecated ###############################################################
@@ -1075,6 +1218,18 @@
def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
return getattr(self.rtype_eids_rdef(rtype, eidfrom, eidto), rprop)
+ @property
+ @deprecated("[3.13] use .cnxset attribute instead of .pool")
+ def pool(self):
+ return self.cnxset
+
+ @deprecated("[3.13] use .set_cnxset() method instead of .set_pool()")
+ def set_pool(self):
+ return self.set_cnxset()
+
+ @deprecated("[3.13] use .free_cnxset() method instead of .reset_pool()")
+ def reset_pool(self):
+ return self.free_cnxset()
@deprecated("[3.7] execute is now unsafe by default in hooks/operation. You"
" can also control security with the security_enabled context "
@@ -1133,20 +1288,21 @@
is_internal_session = True
running_dbapi_query = False
- def __init__(self, repo, cnxprops=None):
+ def __init__(self, repo, cnxprops=None, safe=False):
super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
_id='internal')
self.user._cw = self # XXX remove when "vreg = user._cw.vreg" hack in entity.py is gone
self.cnxtype = 'inmemory'
- self.disable_hook_categories('integrity')
+ if not safe:
+ self.disable_hook_categories('integrity')
@property
- def pool(self):
- """connections pool, set according to transaction mode for each query"""
+ def cnxset(self):
+ """connections set, set according to transaction mode for each query"""
if self.repo.shutting_down:
- self.reset_pool(True)
+ self.free_cnxset(True)
raise ShuttingDown('repository is shutting down')
- return getattr(self._threaddata, 'pool', None)
+ return getattr(self._threaddata, 'cnxset', None)
class InternalManager(object):