Commit ecc0e8db authored by Shane Hathaway's avatar Shane Hathaway

Rearranged the sub-connection closing mechanism. The old method was subject

to database connection leaks when the mount point object was garbage
collected.
parent a051be33
...@@ -84,8 +84,8 @@ ...@@ -84,8 +84,8 @@
############################################################################## ##############################################################################
"""Mounted database support """Mounted database support
$Id: Mount.py,v 1.7 2000/11/07 13:03:32 jim Exp $""" $Id: Mount.py,v 1.8 2000/12/30 20:04:49 shane Exp $"""
__version__='$Revision: 1.7 $'[11:-2] __version__='$Revision: 1.8 $'[11:-2]
import thread, Persistence, Acquisition import thread, Persistence, Acquisition
import ExtensionClass, string, time, sys import ExtensionClass, string, time, sys
...@@ -124,7 +124,6 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit): ...@@ -124,7 +124,6 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit):
# Default values for non-persistent variables. # Default values for non-persistent variables.
_v_db = None _v_db = None
_v_data = None _v_data = None
_v_close_db = 0
_v_connect_error = None _v_connect_error = None
def __init__(self, path, params=None, classDefsFromRoot=1): def __init__(self, path, params=None, classDefsFromRoot=1):
...@@ -193,50 +192,17 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit): ...@@ -193,50 +192,17 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit):
dblock.release() dblock.release()
return db, newMount return db, newMount
def __repr__(self): def _getMountpointId(self):
return "%s %s" % (self.__class__.__name__, self._path) return self.__mountpoint_id
def _close(self): def _getMountParams(self):
# The onCloseCallback handler. return self._params
# Closes a single connection to the database
# and possibly the database itself.
t = self._v_data
if t is not None:
self._v_data = None
data = t[0]
if getattr(data, '_v__object_deleted__', 0):
# This mount point has been deleted.
del data._v__object_deleted__
self._v_close_db = 1
if data is not None:
conn = data._p_jar
if conn is not None:
try: del conn._mount_parent_jar
except: pass
if conn._db is not None:
conn.close()
if self._v_close_db:
# Stop using this database. Close it if no other
# MountPoint is using it.
dblock.acquire()
try:
self._v_close_db = 0
self._v_db = None
params = self._params
if dbs.has_key(params):
dbInfo = dbs[params]
db, mounts = dbInfo
try: del mounts[self.__mountpoint_id]
except: pass
if len(mounts) < 1:
# No more mount points are using this database.
del dbs[params]
db.close()
LOG('ZODB', INFO, 'Closed database: %s' % params)
finally:
dblock.release()
def __openConnection(self, parent): def __repr__(self):
return "%s(%s, %s)" % (self.__class__.__name__, repr(self._path),
self._params)
def _openMountableConnection(self, parent):
# Opens a new connection to the database. # Opens a new connection to the database.
db = self._v_db db = self._v_db
if db is None: if db is None:
...@@ -249,59 +215,60 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit): ...@@ -249,59 +215,60 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit):
# Get _p_jar from parent. # Get _p_jar from parent.
self._p_jar = jar = parent._p_jar self._p_jar = jar = parent._p_jar
conn = db.open(version=jar.getVersion()) conn = db.open(version=jar.getVersion())
# Add an attribute to the connection which # Add an attribute to the connection which
# makes it possible for us to find the primary # makes it possible for us to find the primary
# database connection. See ClassFactoryForMount(). # database connection. See ClassFactoryForMount().
conn._mount_parent_jar = jar conn._mount_parent_jar = jar
try: mcc = MountedConnectionCloser(self, conn)
jar.onCloseCallback(self._close) jar.onCloseCallback(mcc)
obj = self._getMountRoot(conn.root()) return conn, newMount
data = getattr(obj, 'aq_base', obj)
# Store the data object in a tuple to hide from acquisition. def _getObjectFromConnection(self, conn):
self._v_data = (data,) obj = self._getMountRoot(conn.root())
except: data = getattr(obj, 'aq_base', obj)
# Close the connection before processing the exception. # Store the data object in a tuple to hide from acquisition.
del conn._mount_parent_jar self._v_data = (data,)
conn.close()
raise
if newMount:
id = data.id
if callable(id):
id = id()
p = string.join(parent.getPhysicalPath() + (id,), '/')
LOG('ZODB', INFO, 'Mounted database %s at %s' % \
(self._params, p))
return data return data
def __of__(self, parent): def _getOrOpenObject(self, parent):
# Accesses the database, returning an acquisition
# wrapper around the connected object rather than around self.
t = self._v_data t = self._v_data
if t is None: if t is None:
self._v_connect_error = None self._v_connect_error = None
conn = None
try: try:
data = self.__openConnection(parent) conn, newMount = self._openMountableConnection(parent)
data = self._getObjectFromConnection(conn)
if newMount:
id = data.getId()
p = string.join(parent.getPhysicalPath() + (id,), '/')
LOG('ZODB', INFO, 'Mounted database %s at %s' %
(self._getMountParams(), p))
except: except:
self._v_close_db = 1 # Broken database.
if conn is not None:
conn._close_mounted_db = 1
self._logConnectException() self._logConnectException()
# Broken database. Wrap around self. raise
return Acquisition.ImplicitAcquisitionWrapper(self, parent)
else: else:
data = t[0] data = t[0]
return data.__of__(parent) 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)
def _test(self, parent): def _test(self, parent):
'''Tests the database connection. '''Tests the database connection.
''' '''
if self._v_data is None: self._getOrOpenObject(parent)
try:
data = self.__openConnection(parent)
except:
self._v_close_db = 1
self._logConnectException()
raise
return 1 return 1
def _getMountRoot(self, root): def _getMountRoot(self, root):
...@@ -311,22 +278,84 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit): ...@@ -311,22 +278,84 @@ class MountPoint(Persistence.Persistent, Acquisition.Implicit):
try: try:
app = root['Application'] app = root['Application']
except: except:
raise MountedStorageError, \ raise MountedStorageError, (
'No \'Application\' object exists in the mountable database.' "No 'Application' object exists in the mountable database.")
try: try:
return app.unrestrictedTraverse(self._path) return app.unrestrictedTraverse(self._path)
except: except:
raise MountedStorageError, \ raise MountedStorageError, (
('The path \'%s\' was not found in the mountable database.' \ "The path '%s' was not found in the mountable database."
% self._path) % self._path)
def _logConnectException(self): def _logConnectException(self):
'''Records info about the exception that just occurred. '''Records info about the exception that just occurred.
''' '''
from cStringIO import StringIO try:
from cStringIO import StringIO
except:
from StringIO import StringIO
import traceback import traceback
exc = sys.exc_info() exc = sys.exc_info()
LOG('ZODB', WARNING, 'Failed to mount database. %s (%s)' % exc[:2]) LOG('ZODB', WARNING, 'Failed to mount database. %s (%s)' % exc[:2],
error=exc)
f=StringIO() f=StringIO()
traceback.print_tb(exc[2], 100, f) traceback.print_tb(exc[2], 100, f)
self._v_connect_error = (exc[0], exc[1], f.getvalue()) self._v_connect_error = (exc[0], exc[1], f.getvalue())
exc = None
class MountedConnectionCloser:
'''Closes the connection used by the mounted database
while performing other cleanup.
'''
def __init__(self, mountpoint, conn):
# conn is the child connection.
self.mp = mountpoint
self.conn = conn
def __call__(self):
# The onCloseCallback handler.
# Closes a single connection to the database
# and possibly the database itself.
conn = self.conn
close_db = 0
if conn is not None:
mp = self.mp
# Remove potential circular references.
self.conn = None
self.mp = None
# Detect whether we should close the database.
close_db = getattr(conn, '_close_mounted_db', 0)
t = mp._v_data
if t is not None:
mp._v_data = None
data = t[0]
if not close_db and getattr(data, '_v__object_deleted__', 0):
# This mount point has been deleted.
del data._v__object_deleted__
close_db = 1
# Close the child connection.
try: del conn._mount_parent_jar
except: pass
conn.close()
if close_db:
# Stop using this database. Close it if no other
# MountPoint is using it.
dblock.acquire()
try:
params = mp._getMountParams()
mp._v_db = None
if dbs.has_key(params):
dbInfo = dbs[params]
db, mounts = dbInfo
try: del mounts[mp._getMountpointId()]
except: pass
if len(mounts) < 1:
# No more mount points are using this database.
del dbs[params]
db.close()
LOG('ZODB', INFO, 'Closed database: %s' % params)
finally:
dblock.release()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment