Commit a6087fbb authored by Jim Fulton's avatar Jim Fulton

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.
parent 2728a1b5
......@@ -624,7 +624,7 @@ class ClientStorage(object):
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 @@ class ClientStorage(object):
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 @@ class TransactionIterator(object):
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
......
......@@ -99,3 +99,53 @@ class IterationTests:
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()
"""
......@@ -1103,10 +1103,6 @@ slow_test_classes = [
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 @@ def test_suite():
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:
......
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