[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