[Zodb-checkins] SVN: ZODB/trunk/src/ Fixed a serious bug:
Jim Fulton
jim at zope.com
Tue Feb 17 15:04:44 EST 2009
Log message for revision 96649:
Fixed a serious bug:
Packing a blob-enabled file storage in a ZEO server caused blob data
to be lost.
Changed:
U ZODB/trunk/src/CHANGES.txt
U ZODB/trunk/src/ZODB/FileStorage/fspack.py
U ZODB/trunk/src/ZODB/FileStorage/tests.py
-=-
Modified: ZODB/trunk/src/CHANGES.txt
===================================================================
--- ZODB/trunk/src/CHANGES.txt 2009-02-17 19:21:14 UTC (rev 96648)
+++ ZODB/trunk/src/CHANGES.txt 2009-02-17 20:04:44 UTC (rev 96649)
@@ -22,6 +22,15 @@
XXX There are known issues with this implementation that need to be
sorted out before it is "released".
+3.9.0a11 (2009-02-17)
+=====================
+
+Bugs Fixed
+----------
+
+- Packing a blob-enabled file storage in a ZEO server caused blob data
+ to be lost.
+
3.9.0a10 (2009-01-05)
=====================
Modified: ZODB/trunk/src/ZODB/FileStorage/fspack.py
===================================================================
--- ZODB/trunk/src/ZODB/FileStorage/fspack.py 2009-02-17 19:21:14 UTC (rev 96648)
+++ ZODB/trunk/src/ZODB/FileStorage/fspack.py 2009-02-17 20:04:44 UTC (rev 96649)
@@ -267,7 +267,7 @@
# special case, pack to before creation time
continue
raise
-
+
reachable[oid] = pos
for oid in self.findrefs(pos):
if oid not in reachable:
@@ -343,7 +343,7 @@
os.path.join(storage.blob_dir, '.removed'), 'w')
else:
self.pack_blobs = False
-
+
path = storage._file.name
self._name = path
# We open our own handle on the storage so that much of pack can
@@ -501,12 +501,22 @@
if data and ZODB.blob.is_blob_record(data):
# We need to remove the blob record. Maybe we
# need to remove oid:
- if h.oid not in self.gc.reachable:
- self.blob_removed.write(h.oid.encode('hex')+'\n')
- else:
- self.blob_removed.write(
- (h.oid+h.tid).encode('hex')+'\n')
-
+
+ # But first, we need to make sure the record
+ # we're looking at isn't a dup of the current
+ # record. There's a bug in ZEO blob support that causes
+ # duplicate data records.
+ rpos = self.gc.reachable.get(h.oid)
+ is_dup = (rpos
+ and self._read_data_header(rpos).tid == h.tid)
+ if not is_dup:
+ if h.oid not in self.gc.reachable:
+ self.blob_removed.write(
+ h.oid.encode('hex')+'\n')
+ else:
+ self.blob_removed.write(
+ (h.oid+h.tid).encode('hex')+'\n')
+
pos += h.recordlen()
continue
Modified: ZODB/trunk/src/ZODB/FileStorage/tests.py
===================================================================
--- ZODB/trunk/src/ZODB/FileStorage/tests.py 2009-02-17 19:21:14 UTC (rev 96648)
+++ ZODB/trunk/src/ZODB/FileStorage/tests.py 2009-02-17 20:04:44 UTC (rev 96649)
@@ -14,16 +14,18 @@
from zope.testing import doctest
+import cPickle
import os
import time
import transaction
import unittest
+import ZODB.blob
import ZODB.FileStorage
import ZODB.tests.util
def pack_keep_old():
"""Should a copy of the database be kept?
-
+
The pack_keep_old constructor argument controls whether a .old file (and .old directory for blobs is kept.)
>>> fs = ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs')
@@ -61,7 +63,7 @@
True
>>> db.close()
-
+
>>> fs = ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs',
... create=True, pack_keep_old=False)
>>> db = ZODB.DB(fs)
@@ -85,11 +87,44 @@
>>> os.path.exists('blobs.old')
False
>>> db.close()
-
-
"""
+def pack_with_repeated_blob_records():
+ """
+ There is a bug in ZEO that causes duplicate bloc database records
+ to be written in a blob store operation. (Maybe this has been
+ fixed by the time you read this, but there might still be
+ transactions in the wild that have duplicate records.
+ >>> fs = ZODB.FileStorage.FileStorage('t', blob_dir='bobs')
+ >>> db = ZODB.DB(fs)
+ >>> conn = db.open()
+ >>> conn.root()[1] = ZODB.blob.Blob()
+ >>> transaction.commit()
+ >>> tm = transaction.TransactionManager()
+ >>> oid = conn.root()[1]._p_oid
+ >>> blob_record, oldserial = fs.load(oid)
+
+ Now, create a transaction with multiple saves:
+
+ >>> trans = tm.begin()
+ >>> fs.tpc_begin(trans)
+ >>> open('ablob', 'w').write('some data')
+ >>> _ = fs.store(oid, oldserial, blob_record, '', trans)
+ >>> _ = fs.storeBlob(oid, oldserial, blob_record, 'ablob', '', trans)
+ >>> fs.tpc_vote(trans)
+ >>> fs.tpc_finish(trans)
+
+ >>> time.sleep(.01)
+ >>> db.pack()
+
+ >>> conn.sync()
+ >>> conn.root()[1].open().read()
+ 'some data'
+
+ >>> db.close()
+ """
+
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite(
More information about the Zodb-checkins
mailing list