[Zope-CVS] CVS: Products/DBTab - ClassFactories.py:1.2 Mount.py:1.2 MountedObject.py:1.3 version.txt:1.8
Shane Hathaway
shane@zope.com
Fri, 31 Jan 2003 18:34:54 -0500
Update of /cvs-repository/Products/DBTab
In directory cvs.zope.org:/tmp/cvs-serv28145
Modified Files:
ClassFactories.py Mount.py MountedObject.py version.txt
Log Message:
Changed DBTab's mounting strategy so that mounted connections stay bound to
a root connection. This change is designed to:
- eliminate issues with volatile attributes in application code that
cross mount boundaries.
- eliminate the global registry of open connections, which seemed to have a
rare race condition (ugh!)
- go faster. :-) The mount point traversal penalty is much lower now,
since it's now legal to keep a volatile reference to the mounted object.
Updated version.txt.
=== Products/DBTab/ClassFactories.py 1.1.1.1 => 1.2 ===
--- Products/DBTab/ClassFactories.py:1.1.1.1 Tue Oct 15 13:49:19 2002
+++ Products/DBTab/ClassFactories.py Fri Jan 31 18:34:50 2003
@@ -73,12 +73,12 @@
def autoClassFactory(jar, module, name):
"""Class factory with ZClasses and support for central class definitions.
"""
- # If a child of another connection, use the class factory from
- # the parent database, otherwise use the Zope class factory.
- parent_conn = getattr(jar, '_mount_parent_jar', None)
- parent_db = getattr(parent_conn, '_db', None)
- if parent_db is not None:
- return parent_db._classFactory(parent_conn, module, name)
+ # If not the root connection, use the class factory from
+ # the root database, otherwise use the Zope class factory.
+ root_conn = getattr(jar, '_root_connection', None)
+ root_db = getattr(root_conn, '_db', None)
+ if root_db is not None:
+ return root_db._classFactory(root_conn, module, name)
else:
return zopeClassFactory(jar, module, name)
=== Products/DBTab/Mount.py 1.1.1.1 => 1.2 ===
--- Products/DBTab/Mount.py:1.1.1.1 Tue Oct 15 13:49:20 2002
+++ Products/DBTab/Mount.py Fri Jan 31 18:34:50 2003
@@ -23,18 +23,12 @@
from zLOG import LOG, ERROR, INFO, WARNING
-# _open_connections ensures that there is only one child connection
-# for each child database and connection.
-_open_connections = {} # { (parent_jar_id, child_db_name) -> child_jar }
-
-
class MountPoint(Persistence.Persistent, Acquisition.Implicit):
'''The base class for a Zope object which, when traversed,
accesses a different database.
'''
# Default values for non-persistent variables.
- _v_db = None # The open database
_v_data = None # An object in an open connection
_v_connect_error = None
@@ -51,6 +45,11 @@
"""
raise NotImplementedError
+ def _getRootDBName(self):
+ """Hook for getting the name of the root database.
+ """
+ raise NotImplementedError
+
def _traverseToMountedRoot(self, root):
"""Hook for getting the object to be mounted.
"""
@@ -59,37 +58,32 @@
def __repr__(self):
return "%s(id=%s)" % (self.__class__.__name__, self.id)
- def _openMountableConnection(self, parent):
- # Opens a new connection to the database.
- db = self._v_db
- if db is None:
- db = self._getDB()
- jar = self._p_jar
- if jar is None:
- jar = parent._p_jar
- assert jar is not None
- ident = (id(jar), self._getDBName())
- conn = _open_connections.get(ident)
+ def _getMountedConnection(self, anyjar):
+ db_name = self._getDBName()
+ conn = anyjar._getMountedConnection(db_name)
if conn is None:
- conn = db.open(version=jar.getVersion())
- # Add attributes to the connection which make it possible
- # to find the primary database connection and reliably
- # remove from _open_connections.
- conn._mount_parent_jar = jar
- conn._mount_identity = ident
- mcc = MountedConnectionCloser(self, conn)
- jar.onCloseCallback(mcc)
- _open_connections[ident] = conn
+ root_conn = anyjar._getRootConnection()
+ if db_name == self._getRootDBName():
+ conn = root_conn
+ else:
+ conn = self._getDB().open(version=root_conn.getVersion())
+ root_conn._addMountedConnection(db_name, conn)
return conn
+
def _getOrOpenObject(self, parent):
t = self._v_data
- if t is None:
+ if t is not None:
+ data = t[0]
+ else:
self._v_connect_error = None
conn = None
try:
- conn = self._openMountableConnection(parent)
+ anyjar = self._p_jar
+ if anyjar is None:
+ anyjar = parent._p_jar
+ conn = self._getMountedConnection(anyjar)
root = conn.root()
obj = self._traverseToMountedRoot(root)
data = aq_base(obj)
@@ -99,28 +93,27 @@
# Possibly broken database.
self._logConnectException()
raise
- else:
- data = t[0]
- try:
- # Make it possible to find the mount point object by poking
- # an attribute into the mounted object.
- # Also, hide from acquisition in a tuple.
- data._v_mount_point_ = (self,)
- except:
- # Might be a read-only object.
- pass
+ try:
+ # Make it possible to find the mount point object by poking
+ # an attribute into the mounted object.
+ # Also, hide from acquisition in a tuple.
+ data._v_mount_point_ = (self,)
+ except:
+ # Might be a read-only object.
+ pass
return data.__of__(parent)
+
def __of__(self, parent):
# Accesses the database, returning an acquisition
# wrapper around the connected object rather than around self.
try:
return self._getOrOpenObject(parent)
except:
- return Acquisition.ImplicitAcquisitionWrapper(
- self, parent)
+ return Acquisition.ImplicitAcquisitionWrapper(self, parent)
+
def _test(self, parent):
'''Tests the database connection.
@@ -128,6 +121,7 @@
self._getOrOpenObject(parent)
return 1
+
def _logConnectException(self):
'''Records info about the exception that just occurred.
'''
@@ -145,33 +139,65 @@
exc = None
-class MountedConnectionCloser:
- '''Closes the connection used by the mounted database
- while performing other cleanup.
- '''
- def __init__(self, mp, conn):
- # conn is the child connection.
- self.mp = mp
- self.conn = conn
-
- def __call__(self):
- # The onCloseCallback handler.
- # Closes a single connection to the database.
- conn = self.conn
- if conn is not None:
- mp = self.mp
- # Remove potential circular references.
- self.mp = None
- self.conn = None
- # Remove from _open_connections.
- ident = conn._mount_identity
- try: del _open_connections[ident]
- except KeyError: pass
- # Clear the mount point's reference to the connection.
- try: del mp._v_data
- except: pass
- # Close the child connection.
- try: del conn._mount_parent_jar
- except: pass
- conn.close()
+
+class ConnectionPatches:
+ # Changes to Connection.py that might fold into ZODB
+
+ _root_connection = None
+ _mounted_connections = None
+
+ def _getRootConnection(self):
+ root_conn = self._root_connection
+ if root_conn is None:
+ return self
+ else:
+ return root_conn
+
+ def _getMountedConnection(self, name):
+ conns = self._getRootConnection()._mounted_connections
+ if conns is None:
+ return None
+ else:
+ return conns.get(name)
+
+ def _addMountedConnection(self, name, conn):
+ if conn._root_connection is not None:
+ raise ValueError, 'Connection %s is already mounted' % repr(conn)
+ root_conn = self._getRootConnection()
+ conns = root_conn._mounted_connections
+ if conns is None:
+ conns = {}
+ root_conn._mounted_connections = conns
+ if conns.has_key(name):
+ raise KeyError, 'A connection named %s already exists' % repr(name)
+ conn._root_connection = root_conn
+ conns[name] = conn
+
+ def _setDB(self, odb):
+ self._real_setDB(odb)
+ conns = self._mounted_connections
+ if conns:
+ for conn in conns.values():
+ conn._setDB(conn._db)
+
+ def close(self):
+ self._real_close()
+ conns = self._mounted_connections
+ if conns:
+ for conn in conns.values():
+ conn._incrgc() # This is a good time to do some GC
+ # XXX maybe we ought to call the close callbacks.
+ conn._storage = conn._tmp = conn.new_oid = conn._opened = None
+ conn._debug_info = ()
+ # The mounted connection keeps a reference to
+ # its database, but nothing else.
+
+if 1:
+ # patch Connection.py.
+ from ZODB.Connection import Connection
+ Connection._real_setDB = Connection._setDB
+ Connection._real_close = Connection.close
+
+ for k, v in ConnectionPatches.__dict__.items():
+ setattr(Connection, k, v)
=== Products/DBTab/MountedObject.py 1.2 => 1.3 ===
--- Products/DBTab/MountedObject.py:1.2 Wed Oct 16 17:13:04 2002
+++ Products/DBTab/MountedObject.py Fri Jan 31 18:34:50 2003
@@ -102,6 +102,11 @@
"""
return getConfiguration().getDatabaseFactory(self._path).getName()
+ def _getRootDBName(self):
+ """Hook for getting the name of the root database.
+ """
+ return getConfiguration().getDatabaseFactory('/').getName()
+
def _loadMountParams(self):
factory = getConfiguration().getDatabaseFactory(self._path)
params = factory.getMountParams(self._path)
=== Products/DBTab/version.txt 1.7 => 1.8 ===
--- Products/DBTab/version.txt:1.7 Fri Jan 10 15:10:59 2003
+++ Products/DBTab/version.txt Fri Jan 31 18:34:50 2003
@@ -1 +1 @@
-DBTab-1.0.2
+DBTab-1.1-unreleased