Skip to content

Commit f02701b

Browse files
authored
FileStorage: Fix restore to correctly check restored data content (#409)
In commit 8160568 (FileStorage: fix rare data corruption when using restore after multiple undos (#395)) we fixed FileStorage.restore to work correctly in the presence of multiple undos for the same oid in the same transaction by reworking oid data records of one transaction to be all scanned in the loop. But unfortunately that commit introduced a regression: When restore is instructed to do an undo, in other words, to make a copy of oid data by referring to that data by backpointer, it checks content of the pointed-to record, and if data content there does not match data content passed to restore call, the copy-via-backpointer optimization is silently disabled. However 8160568 contained a thinko when doing the check: after discovering pointed-to oid data record, the intent there it was to load actual data from there, but the loading was performed not from the offset where the data lives, but from then beginning of some next data-record header instead. As the result the "copy-via-backpointer" was always disabled. -> Fix that by seeking to correct position before loading the data. I originally discovered this issue by running Zodbtools tests wrt ZODB6 and getting failures for `zodb restore` related tests with difference in between restored and expected states as e.g. @@ -296,15 +296,21 @@ txn 0285cbad77777800 " " user "root1.0\nYour\nMagesty " description "undo 1.0\nmore detailed description\n\nzzz ...\t" extension "" -obj 0000000000000005 from 0285cbad6962fd19 +obj 0000000000000005 30 sha1:5fe26f63145e7ad5ac7c2a6bc813e706c5548278 +c__main__ +Object +q^A.U^Ea1.22q^B. /cc @d-maurer /reviewed-by @perrinjerome /reviewed-on #409
1 parent 0ded64d commit f02701b

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99

1010
- repozo: prevent an incorrect "option ignored" warning when running backup or verify.
1111

12+
- FileStorage: fix `restore` regression introduced in ZODB 6.0 in `#395
13+
<https://github.com/zopefoundation/ZODB/pull/395>`_: when restoring data
14+
records with undo the `restore` was no longer emitting backpointers and was
15+
emitting duplicate data copies instead. `#409 <https://github.com/zopefoundation/ZODB/pull/409>`_
16+
fixes `restore` back to emit data records with backpointers for undo again.
17+
1218

1319
6.0 (2024-03-20)
1420
================

src/ZODB/FileStorage/FileStorage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ def _data_find(self, tpos, oid, data):
704704
logger.error("Mismatch between data and"
705705
" backpointer at %d", pos)
706706
return 0
707+
self._file.seek(data_pos + DATA_HDR_LEN)
707708
_data = self._file.read(data_hdr.plen)
708709
if data != _data:
709710
return 0

src/ZODB/tests/testFileStorage.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from ZODB._compat import dumps
3636
from ZODB.Connection import TransactionMetaData
3737
from ZODB.fsIndex import fsIndex
38+
from ZODB.interfaces import IStorageWrapper
3839
from ZODB.tests import BasicStorage
3940
from ZODB.tests import ConflictResolution
4041
from ZODB.tests import Corruption
@@ -242,6 +243,46 @@ def testRestoreBumpsOid(self):
242243
# Before ZODB 3.2.6, this failed, with ._oid == z64.
243244
self.assertEqual(self._storage._oid, giant_oid)
244245

246+
def testRestoreCopy(self):
247+
# Verify that restore with prev_txn restores data record with
248+
# backpointer to prev_txn.
249+
oid = p64(1)
250+
251+
t = TransactionMetaData()
252+
self._storage.tpc_begin(t)
253+
self._storage.store(oid, z64, b'data rev 1', b'', t)
254+
self._storage.tpc_vote(t)
255+
self._storage.tpc_finish(t)
256+
rev1 = self._storage.lastTransaction()
257+
258+
rev2 = p64(U64(rev1)+1)
259+
t = TransactionMetaData()
260+
self._storage.tpc_begin(t, rev2)
261+
# restore should write data record with backpointer to rev1
262+
self._storage.restore(oid, rev2, b'data rev 1', b'', rev1, t)
263+
self._storage.tpc_vote(t)
264+
self._storage.tpc_finish(t)
265+
self.assertEqual(self._storage.lastTransaction(), rev2)
266+
267+
# locate rev2 data record and verify it is backpointer to rev1
268+
pos2 = self._storage._lookup_pos(oid)
269+
h2 = self._storage._read_data_header(pos2, oid)
270+
self.assertEqual(h2.oid, oid)
271+
self.assertEqual(h2.tid, rev2)
272+
self.assertEqual(h2.plen, 0)
273+
self.assertNotEqual(h2.back, 0)
274+
275+
h1 = self._storage._read_data_header(h2.back, oid)
276+
self.assertEqual(h1.oid, oid)
277+
self.assertEqual(h1.tid, rev1)
278+
self.assertNotEqual(h1.plen, 0)
279+
self.assertEqual(h1.back, 0)
280+
281+
data1 = self._storage._file.read(h1.plen)
282+
if IStorageWrapper.providedBy(self._storage):
283+
data1 = self._storage.untransform_record_data(data1)
284+
self.assertEqual(data1, b'data rev 1')
285+
245286
def testCorruptionInPack(self):
246287
# This sets up a corrupt .fs file, with a redundant transaction
247288
# length mismatch. The implementation of pack in many releases of

0 commit comments

Comments
 (0)