[Zodb-checkins] SVN: ZODB/trunk/src/ZEO/ Fixed a bug in the iterator support. Iterators weren't properly

Jim Fulton jim at zope.com
Sun Nov 16 12:11:45 EST 2008


Log message for revision 93014:
  Fixed a bug in the iterator support.  Iterators weren't properly
  cleaned up on disconnect.
  
  Also avoid unnecessary iterator_gc calls to the server when there are
  no iterators to be cleaned up.
  

Changed:
  U   ZODB/trunk/src/ZEO/ClientStorage.py
  U   ZODB/trunk/src/ZEO/tests/IterationTests.py
  U   ZODB/trunk/src/ZEO/tests/testZEO.py

-=-
Modified: ZODB/trunk/src/ZEO/ClientStorage.py
===================================================================
--- ZODB/trunk/src/ZEO/ClientStorage.py	2008-11-16 17:11:43 UTC (rev 93013)
+++ ZODB/trunk/src/ZEO/ClientStorage.py	2008-11-16 17:11:45 UTC (rev 93014)
@@ -624,7 +624,7 @@
         self._ready.clear()
         self._server = disconnected_stub
         self._midtxn_disconnect = 1
-        self._iterator_gc()
+        self._iterator_gc(True)
 
     def __len__(self):
         """Return the size of the storage."""
@@ -1380,19 +1380,26 @@
         self._iterators.pop(iid, None)
         self._iterator_ids.remove(iid)
 
-    def _iterator_gc(self):
+    def _iterator_gc(self, disconnected=False):
+        if not self._iterator_ids:
+            return
+
+        if disconnected:
+            for i in self._iterators.values():
+                i._iid = -1
+            self._iterators.clear()
+            self._iterator_ids.clear()
+            return
+
         iids = self._iterator_ids - set(self._iterators)
-        try:
-            self._server.iterator_gc(list(iids))
-        except ClientDisconnected:
-            # We could not successfully garbage-collect iterators.
-            # The server might have been restarted, so the IIDs might mean
-            # something different now. We simply forget our unused IIDs to
-            # avoid gc'ing foreign iterators.
-            # In the case that the server was not restarted, we accept the
-            # risk of leaking resources on the ZEO server.
-            pass
-        self._iterator_ids -= iids
+        if iids:
+            try:
+                self._server.iterator_gc(list(iids))
+            except ClientDisconnected:
+                # If we get disconnected, all of the iterators on the
+                # server are thrown away.  We should clear ours too:
+                return self._iterator_gc(True)
+            self._iterator_ids -= iids
 
 
 class TransactionIterator(object):
@@ -1409,6 +1416,9 @@
         if self._ended:
             raise ZODB.interfaces.StorageStopIteration()
 
+        if self._iid < 0:
+            raise ClientDisconnected("Disconnected iterator")
+
         tx_data = self._storage._server.iterator_next(self._iid)
         if tx_data is None:
             # The iterator is exhausted, and the server has already

Modified: ZODB/trunk/src/ZEO/tests/IterationTests.py
===================================================================
--- ZODB/trunk/src/ZEO/tests/IterationTests.py	2008-11-16 17:11:43 UTC (rev 93013)
+++ ZODB/trunk/src/ZEO/tests/IterationTests.py	2008-11-16 17:11:45 UTC (rev 93014)
@@ -99,3 +99,53 @@
         self.assertEquals(txn_info1.tid, txn_info2.tid)
         self.assertRaises(StopIteration, iter1.next)
         self.assertRaises(StopIteration, iter2.next)
+
+def iterator_sane_after_reconnect():
+    r"""Make sure that iterators are invalidated on disconnect.
+
+Start a server:
+
+    >>> addr, adminaddr = start_server(
+    ...     '<filestorage>\npath fs\n</filestorage>', keep=1)
+
+Open a client storage to it and commit a some transactions:
+
+    >>> import ZEO, transaction
+    >>> db = ZEO.DB(addr)
+    >>> conn = db.open()
+    >>> for i in range(10):
+    ...     conn.root().i = i
+    ...     transaction.commit()
+
+Create an iterator:
+
+    >>> it = conn._storage.iterator()
+    >>> tid1 = it.next().tid
+
+Restart the storage:
+
+    >>> stop_server(adminaddr)
+    >>> wait_disconnected(conn._storage)
+    >>> _ = start_server('<filestorage>\npath fs\n</filestorage>', addr=addr)
+    >>> wait_connected(conn._storage)
+
+Now, we'll create a second iterator:
+
+    >>> it2 = conn._storage.iterator()
+
+If we try to advance the first iterator, we should get an error:
+
+    >>> it.next().tid > tid1
+    Traceback (most recent call last):
+    ...
+    ClientDisconnected: Disconnected iterator
+
+The second iterator should be peachy:
+
+    >>> it2.next().tid == tid1
+    True
+
+Cleanup:
+
+    >>> db.close()
+    """

Modified: ZODB/trunk/src/ZEO/tests/testZEO.py
===================================================================
--- ZODB/trunk/src/ZEO/tests/testZEO.py	2008-11-16 17:11:43 UTC (rev 93013)
+++ ZODB/trunk/src/ZEO/tests/testZEO.py	2008-11-16 17:11:45 UTC (rev 93014)
@@ -1103,10 +1103,6 @@
     
 quick_test_classes = [FileStorageRecoveryTests, ConfigurationTests]
 
-def setUp(test):
-    ZODB.tests.util.setUp(test)
-    test.globs['get_port'] = lambda : get_port(test)
-
 def test_suite():
     suite = unittest.TestSuite()
 
@@ -1115,12 +1111,14 @@
     zeo = unittest.TestSuite()
     zeo.addTest(unittest.makeSuite(ZODB.tests.util.AAAA_Test_Runner_Hack))
     zeo.addTest(doctest.DocTestSuite(
-        setUp=setUp, tearDown=zope.testing.setupstack.tearDown))
+        setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown))
+    zeo.addTest(doctest.DocTestSuite(ZEO.tests.IterationTests,
+        setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown))
     zeo.addTest(doctest.DocFileSuite('registerDB.test'))
     zeo.addTest(
         doctest.DocFileSuite(
-            'zeo-fan-out.test',
-            setUp=setUp, tearDown=zope.testing.setupstack.tearDown,
+            'zeo-fan-out.test', 'zdoptions.test',
+            setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown,
             ),
         )
     for klass in quick_test_classes:



More information about the Zodb-checkins mailing list