[Zope-Checkins] SVN: Zope/branches/jim-fix-zclasses/lib/python/ZODB/Connection.py Changed the strategy for handling invalidation of classes.

Jim Fulton jim at zope.com
Mon Feb 7 07:35:56 EST 2005


Log message for revision 29067:
  Changed the strategy for handling invalidation of classes.
  No-longer use setklassstate.  Instead, just call _p_invalidate, as
  with any other object.  This changes didn't break any tests, so I
  assume that this was untested. :(
  
  Changed the strategy for invalidating objects.  Non-ghostifiable
  objects will load their state when they are invalidated.  We have to
  worry about other invalidations that come in while this is happening.
  See the comment in _flush_invalidations.
  
  We need to force all invalidations to take this into account, by going
  through _flush_invalidations.  I haven't done this yet, but I have
  left some XXX comments in places where it needs to be done to remind
  myself that this needs to be done.
  

Changed:
  U   Zope/branches/jim-fix-zclasses/lib/python/ZODB/Connection.py

-=-
Modified: Zope/branches/jim-fix-zclasses/lib/python/ZODB/Connection.py
===================================================================
--- Zope/branches/jim-fix-zclasses/lib/python/ZODB/Connection.py	2005-02-07 12:35:54 UTC (rev 29066)
+++ Zope/branches/jim-fix-zclasses/lib/python/ZODB/Connection.py	2005-02-07 12:35:56 UTC (rev 29067)
@@ -141,8 +141,7 @@
         tpc_finish, tpc_abort, sortKey, abort, commit, commit_sub,
         abort_sub
       - `Database Invalidation Methods`: invalidate, _setDB
-      - `IPersistentDataManager Methods`: setstate, register,
-        setklassstate
+      - `IPersistentDataManager Methods`: setstate, register
       - `Other Methods`: oldstate, exchange, getDebugInfo, setDebugInfo,
         getTransferCounts
 
@@ -219,15 +218,17 @@
         # from a single transaction should be applied atomically, so
         # the lock must be held when reading _invalidated.
 
-        # XXX It sucks that we have to hold the lock to read
+        # It sucks that we have to hold the lock to read
         # _invalidated.  Normally, _invalidated is written by calling
         # dict.update, which will execute atomically by virtue of the
         # GIL.  But some storage might generate oids where hash or
         # compare invokes Python code.  In that case, the GIL can't
         # save us.
+
         self._inv_lock = threading.Lock()
         self._invalidated = d = {}
         self._invalid = d.has_key
+        
 
         # We intend to prevent committing a transaction in which
         # ReadConflictError occurs.  _conflicts is the set of oids that
@@ -429,6 +430,9 @@
         # A Connection is only ever associated with a single DB
         # and Storage.
 
+        # YYY This is a holdover from Python 1.  It is trying to break
+        # cycles
+
         self._db = odb
         self._storage = odb._storage
         self._sortKey = odb._storage.sortKey
@@ -473,6 +477,14 @@
                 del obj._p_jar
                 del obj._p_oid
             else:
+                
+                # XXX Need to change strategy to acount for
+                # non-ghostifiables that need to load right away.
+                # We need to simply call self.invalidate and let
+                # _flush_invalidations actually perform the
+                # invalidations. I'd do this now if I had a test for
+                # it. :)                
+
                 self._cache.invalidate(oid)
 
         self._tpc_cleanup()
@@ -683,6 +695,13 @@
         self._storage = self._tmp
         self._tmp = None
 
+                
+        # XXX Need to change strategy to acount for non-ghostifiables
+        # that need to load right away.  We need to simply call
+        # self.invalidate and let _flush_invalidations actually
+        # perform the invalidations. I'd do this now if I had a test
+        # for it. :)
+
         self._cache.invalidate(src._index.keys())
         self._invalidate_creating(src._creating)
 
@@ -747,11 +766,43 @@
     def _flush_invalidations(self):
         self._inv_lock.acquire()
         try:
-            self._cache.invalidate(self._invalidated)
-            self._invalidated.clear()
+            
+            # Non-ghostifiable objects may need to read when they are
+            # invalidated, so, we'll quickly just replace the
+            # invalidating dict with a new one.  We'll then process
+            # the invalidations after freeing the lock *and* after
+            # reseting the time.  This means that invalidations will
+            # happen after the start of the transactions.  They are
+            # subject to conflict errors and to reading old data,
+
+            # XXX There is a potential problem lurking for persistent
+            # classes.  Suppose we have an invlidation of a persistent
+            # class and of an instance.  If the instance is
+            # invalidated first and if the invalidation logic uses
+            # data read from the class, then the invalidation could
+            # be performed with state data.  Or, suppose that there
+            # are instances of the class that are freed as a result of
+            # invalidating some object.  Perhaps code in their __del__
+            # uses class data.  Really, the only way to properly fix
+            # this is to, in fact, make classes ghostifiable.  Then
+            # we'd have to reimplement attribute lookup to check the
+            # class state and, if necessary, activate the class.  It's
+            # much worse than that though, because we'd also need to
+            # deal with slots.  When a class is ghostified, we'd need
+            # to replace all of the slot operations with versions that
+            # reloaded the object when caled. It's hard to say which
+            # is better for worse.  For now, it seems the risk of
+            # using a class while objects are being invalidated seems
+            # small enough t be acceptible.
+
+            invalidated = self._invalidated
+            self._invalidated = {}
             self._txn_time = None
         finally:
             self._inv_lock.release()
+
+        self._cache.invalidate(invalidated)
+
         # Now is a good time to collect some garbage
         self._cache.incrgc()
 
@@ -841,7 +892,9 @@
         # because we have to check again after the load anyway.
 
         if (obj._p_oid in self._invalidated
-            and not myhasattr(obj, "_p_independent")):
+            and not myhasattr(obj, "_p_independent")
+            and not self._invalidated
+            ):
             # If the object has _p_independent(), we will handle it below.
             self._load_before_or_conflict(obj)
             return
@@ -939,27 +992,6 @@
         p = self._storage.loadSerial(obj._p_oid, tid)
         return self._reader.getState(p)
 
-    def setklassstate(self, obj):
-        # Special case code to handle ZClasses, I think.
-        # Called the cache when an object of type type is invalidated.
-        try:
-            oid = obj._p_oid
-            p, serial = self._storage.load(oid, self._version)
-
-            # We call getGhost(), but we actually get a non-ghost back.
-            # The object is a class, which can't actually be ghosted.
-            copy = self._reader.getGhost(p)
-            obj.__dict__.clear()
-            obj.__dict__.update(copy.__dict__)
-
-            obj._p_oid = oid
-            obj._p_jar = self
-            obj._p_changed = 0
-            obj._p_serial = serial
-        except:
-            self._log.error("setklassstate failed", exc_info=sys.exc_info())
-            raise
-
     def tpc_begin(self, transaction, sub=False):
         self._modified = []
 
@@ -1049,6 +1081,13 @@
         if self._import:
             self._import = None
         self._storage.tpc_abort(transaction)
+                
+        # XXX Need to change strategy to acount for non-ghostifiables
+        # that need to load right away.  We need to simply call
+        # self.invalidate and let _flush_invalidations actually
+        # perform the invalidations. I'd do this now if I had a test
+        # for it. :)
+
         self._cache.invalidate(self._modified)
         self._invalidate_creating()
         while self._added:



More information about the Zope-Checkins mailing list