[Zodb-checkins] SVN: ZODB/branches/3.4/ Plug leaks in the ZEO client cache.

Tim Peters tim.one at comcast.net
Thu Aug 4 16:42:42 EDT 2005


Log message for revision 37713:
  Plug leaks in the ZEO client cache.
  
  ClientCache._evicted():  When deleting the last range of
  non-current tids for an oid, remove the list from the
  noncurrent dict instead of leaving an empty list sitting
  there forever.  This also required adjusting the side-
  effect dance in ClientCache._remove_noncurrent_revisions().
  
  FileCache._makeroom():  This was the major leak -- it
  neglected to remove an evicted object's Entry from the
  key2entry dict.
  

Changed:
  U   ZODB/branches/3.4/NEWS.txt
  U   ZODB/branches/3.4/src/ZEO/cache.py

-=-
Modified: ZODB/branches/3.4/NEWS.txt
===================================================================
--- ZODB/branches/3.4/NEWS.txt	2005-08-04 18:59:21 UTC (rev 37712)
+++ ZODB/branches/3.4/NEWS.txt	2005-08-04 20:42:41 UTC (rev 37713)
@@ -5,6 +5,7 @@
 Following are dates of internal releases (to support ongoing Zope 2
 development) since ZODB 3.4's last public release:
 
+- 3.4.1b3 04-Aug-2005
 - 3.4.1b2 02-Aug-2005
 - 3.4.1b1 26-Jul-2005
 - 3.4.1a6 19-Jul-2005
@@ -31,6 +32,13 @@
   marked a savepoint as invalid after its first use.  The implementation has
   been repaired, to match the docs.
 
+ZEO client cache
+----------------
+
+- (3.4.1b3) Two memory leaks in the ZEO client cache were repaired, a
+  major one involving ``ZEO.cache.Entry`` objects, and a minor one involving
+  empty lists.
+
 Subtransactions
 ---------------
 

Modified: ZODB/branches/3.4/src/ZEO/cache.py
===================================================================
--- ZODB/branches/3.4/src/ZEO/cache.py	2005-08-04 18:59:21 UTC (rev 37712)
+++ ZODB/branches/3.4/src/ZEO/cache.py	2005-08-04 20:42:41 UTC (rev 37713)
@@ -306,7 +306,10 @@
                 # 0x1E = invalidate (hit, discarding current or non-current)
                 self._trace(0x1E, oid, version, tid)
                 self.fc.remove((oid, old_tid))
-            del self.noncurrent[oid]
+            # fc.remove() calling back to _evicted() should have removed
+            # the list from noncurrent when the last non-current revision
+            # was removed.
+            assert oid not in self.noncurrent
 
     ##
     # If `tid` is None, or we have data for `oid` in a (non-empty) version,
@@ -433,7 +436,15 @@
             # we never expect the list to be very long.  So the
             # brute force approach should normally be fine.
             L = self.noncurrent[oid]
-            L.remove((o.start_tid, o.end_tid))
+            element = (o.start_tid, o.end_tid)
+            if len(L) == 1:
+                # We don't want to leave an empty list in the dict:  if
+                # the oid is never referenced again, it would consume RAM
+                # forever more for no purpose.
+                assert L[0] == element
+                del self.noncurrent[oid]
+            else:
+                L.remove(element)
 
     # If `path` isn't None (== we're using a persistent cache file), and
     # envar ZEO_CACHE_TRACE is set to a non-empty value, try to open
@@ -890,8 +901,8 @@
     # the end of the file, currentofs is reset to ZEC3_HEADER_SIZE first.
     # The number of bytes actually freed may be (and probably will be)
     # greater than nbytes, and is _makeroom's return value.  The file is not
-    # altered by _makeroom.  filemap is updated to reflect the
-    # evictions, and it's the caller's responsibilty both to fiddle
+    # altered by _makeroom.  filemap and key2entry are updated to reflect the
+    # evictions, and it's the caller's responsibility both to fiddle
     # the file, and to update filemap, to account for all the space
     # freed (starting at currentofs when _makeroom returns, and
     # spanning the number of bytes retured by _makeroom).
@@ -903,6 +914,7 @@
         while nbytes > 0:
             size, e = self.filemap.pop(ofs)
             if e is not None:
+                del self.key2entry[e.key]
                 self._evictobj(e, size)
             ofs += size
             nbytes -= size
@@ -912,7 +924,8 @@
     # Write Object obj, with data, to file starting at currentofs.
     # nfreebytes are already available for overwriting, and it's
     # guranteed that's enough.  obj.offset is changed to reflect the
-    # new data record position, and filemap is updated to match.
+    # new data record position, and filemap and key2entry are updated to
+    # match.
     def _writeobj(self, obj, nfreebytes):
         size = OBJECT_HEADER_SIZE + obj.size
         assert size <= nfreebytes



More information about the Zodb-checkins mailing list