Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
zodb
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Joshua
zodb
Commits
75bae1a6
Commit
75bae1a6
authored
Jul 12, 2016
by
Jim Fulton
Committed by
GitHub
Jul 12, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #89 from zopefoundation/undo-refactor
Refactored FileStorage transactional undo
parents
e080bdcc
b563487e
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
87 additions
and
31 deletions
+87
-31
CHANGES.rst
CHANGES.rst
+6
-0
src/ZODB/ConflictResolution.py
src/ZODB/ConflictResolution.py
+6
-3
src/ZODB/FileStorage/FileStorage.py
src/ZODB/FileStorage/FileStorage.py
+53
-28
src/ZODB/tests/TransactionalUndoStorage.py
src/ZODB/tests/TransactionalUndoStorage.py
+22
-0
No files found.
CHANGES.rst
View file @
75bae1a6
...
@@ -2,6 +2,12 @@
...
@@ -2,6 +2,12 @@
Change History
Change History
================
================
4.4.3 (unreleased)
==================
- Internal FileStorage-undo fixes that should allow undo in some cases
where it didn't work before.
4.4.2 (2016-07-08)
4.4.2 (2016-07-08)
==================
==================
...
...
src/ZODB/ConflictResolution.py
View file @
75bae1a6
...
@@ -18,7 +18,8 @@ import six
...
@@ -18,7 +18,8 @@ import six
import
zope.interface
import
zope.interface
from
ZODB.POSException
import
ConflictError
from
ZODB.POSException
import
ConflictError
from
ZODB.loglevels
import
BLATHER
from
ZODB.loglevels
import
BLATHER
from
ZODB._compat
import
BytesIO
,
PersistentUnpickler
,
PersistentPickler
,
_protocol
from
ZODB._compat
import
(
BytesIO
,
PersistentUnpickler
,
PersistentPickler
,
_protocol
)
# Subtle: Python 2.x has pickle.PicklingError and cPickle.PicklingError,
# Subtle: Python 2.x has pickle.PicklingError and cPickle.PicklingError,
# and these are unrelated classes! So we shouldn't use pickle.PicklingError,
# and these are unrelated classes! So we shouldn't use pickle.PicklingError,
...
@@ -73,7 +74,8 @@ def state(self, oid, serial, prfactory, p=''):
...
@@ -73,7 +74,8 @@ def state(self, oid, serial, prfactory, p=''):
p
=
p
or
self
.
loadSerial
(
oid
,
serial
)
p
=
p
or
self
.
loadSerial
(
oid
,
serial
)
p
=
self
.
_crs_untransform_record_data
(
p
)
p
=
self
.
_crs_untransform_record_data
(
p
)
file
=
BytesIO
(
p
)
file
=
BytesIO
(
p
)
unpickler
=
PersistentUnpickler
(
find_global
,
prfactory
.
persistent_load
,
file
)
unpickler
=
PersistentUnpickler
(
find_global
,
prfactory
.
persistent_load
,
file
)
unpickler
.
load
()
# skip the class tuple
unpickler
.
load
()
# skip the class tuple
return
unpickler
.
load
()
return
unpickler
.
load
()
...
@@ -241,7 +243,8 @@ def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle,
...
@@ -241,7 +243,8 @@ def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle,
prfactory
=
PersistentReferenceFactory
()
prfactory
=
PersistentReferenceFactory
()
newpickle
=
self
.
_crs_untransform_record_data
(
newpickle
)
newpickle
=
self
.
_crs_untransform_record_data
(
newpickle
)
file
=
BytesIO
(
newpickle
)
file
=
BytesIO
(
newpickle
)
unpickler
=
PersistentUnpickler
(
find_global
,
prfactory
.
persistent_load
,
file
)
unpickler
=
PersistentUnpickler
(
find_global
,
prfactory
.
persistent_load
,
file
)
meta
=
unpickler
.
load
()
meta
=
unpickler
.
load
()
if
isinstance
(
meta
,
tuple
):
if
isinstance
(
meta
,
tuple
):
klass
=
meta
[
0
]
klass
=
meta
[
0
]
...
...
src/ZODB/FileStorage/FileStorage.py
View file @
75bae1a6
...
@@ -787,13 +787,15 @@ class FileStorage(
...
@@ -787,13 +787,15 @@ class FileStorage(
"""Return the tid, data pointer, and data for the oid record at pos
"""Return the tid, data pointer, and data for the oid record at pos
"""
"""
if
tpos
:
if
tpos
:
pos
=
tpos
-
self
.
_pos
-
self
.
_thl
itpos
=
tpos
-
self
.
_pos
-
self
.
_thl
pos
=
tpos
tpos
=
self
.
_tfile
.
tell
()
tpos
=
self
.
_tfile
.
tell
()
h
=
self
.
_tfmt
.
_read_data_header
(
pos
,
oid
)
h
=
self
.
_tfmt
.
_read_data_header
(
it
pos
,
oid
)
afile
=
self
.
_tfile
afile
=
self
.
_tfile
else
:
else
:
h
=
self
.
_read_data_header
(
pos
,
oid
)
h
=
self
.
_read_data_header
(
pos
,
oid
)
afile
=
self
.
_file
afile
=
self
.
_file
if
h
.
oid
!=
oid
:
if
h
.
oid
!=
oid
:
raise
UndoError
(
"Invalid undo transaction id"
,
oid
)
raise
UndoError
(
"Invalid undo transaction id"
,
oid
)
...
@@ -830,7 +832,7 @@ class FileStorage(
...
@@ -830,7 +832,7 @@ class FileStorage(
pointer 0.
pointer 0.
"""
"""
copy
=
1
# Can we just copy a data pointer
copy
=
True
# Can we just copy a data pointer
# First check if it is possible to undo this record.
# First check if it is possible to undo this record.
tpos
=
self
.
_tindex
.
get
(
oid
,
0
)
tpos
=
self
.
_tindex
.
get
(
oid
,
0
)
...
@@ -838,36 +840,55 @@ class FileStorage(
...
@@ -838,36 +840,55 @@ class FileStorage(
tipos
=
tpos
or
ipos
tipos
=
tpos
or
ipos
if
tipos
!=
pos
:
if
tipos
!=
pos
:
# Eek, a later transaction modified the data, but,
# The transaction being undone isn't current because:
# maybe it is pointing at the same data we are.
# a) A later transaction was committed ipos != pos, or
ctid
,
cdataptr
,
cdata
=
self
.
_undoDataInfo
(
oid
,
ipos
,
tpos
)
# b) A change was made in the current transaction. This
# could only be a previous undo in a multi-undo.
# (We don't allow multiple data managers with the same
# storage to participate in the same transaction.)
assert
tipos
>
pos
# Get current data, as identified by tipos. We'll use
# it to decide if and how we can undo in this case.
ctid
,
cdataptr
,
current_data
=
self
.
_undoDataInfo
(
oid
,
ipos
,
tpos
)
if
cdataptr
!=
pos
:
if
cdataptr
!=
pos
:
# We aren't sure if we are talking about the same data
# if cdataptr was == pos, then we'd be cool, because
# we're dealing with the same data.
# Because they aren't equal, we have to dig deeper
# Let's see if data to be undone and current data
# are the same. If not, we'll have to decide whether
# we should try conflict resolution.
try
:
try
:
if
(
data_to_be_undone
=
self
.
_loadBack_impl
(
oid
,
pos
)[
0
]
# The current record wrote a new pickle
if
not
current_data
:
cdataptr
==
tipos
current_data
=
self
.
_loadBack_impl
(
oid
,
cdataptr
)[
0
]
or
# Backpointers are different
if
data_to_be_undone
!=
current_data
:
self
.
_loadBackPOS
(
oid
,
pos
)
!=
# OK, so the current data is different from
self
.
_loadBackPOS
(
oid
,
cdataptr
)
# the data being undone. We can't just copy:
):
copy
=
False
if
pre
and
not
tpos
:
copy
=
0
# we'll try to do conflict resolution
if
not
pre
:
else
:
# The transaction we're undoing has no
# We bail if:
# previous state to merge with, so we
# - We don't have a previous record, which should
# can't resolve a conflict.
# be impossible.
raise
UndoError
(
raise
UndoError
(
"no previous record"
,
oid
)
"Can't undo an add transaction followed by"
" conflicting transactions."
,
oid
)
except
KeyError
:
except
KeyError
:
# LoadBack gave us a key error. Bail.
# LoadBack gave us a key error. Bail.
raise
UndoError
(
"_loadBack() failed"
,
oid
)
raise
UndoError
(
"_loadBack() failed"
,
oid
)
# Return the data that should be written in the undo record.
# Return the data that should be written in the undo record.
if
not
pre
:
if
not
pre
:
# There is no previous revision, because the object creation
# We're undoing object addition. We're doing this because
# is being undone.
# subsequent transactions has no net effect on the state
# (possibly because some of them were undos).
return
""
,
0
,
ipos
return
""
,
0
,
ipos
if
copy
:
if
copy
:
...
@@ -875,12 +896,14 @@ class FileStorage(
...
@@ -875,12 +896,14 @@ class FileStorage(
return
""
,
pre
,
ipos
return
""
,
pre
,
ipos
try
:
try
:
b
data
=
self
.
_loadBack_impl
(
oid
,
pre
)[
0
]
pre_
data
=
self
.
_loadBack_impl
(
oid
,
pre
)[
0
]
except
KeyError
:
except
KeyError
:
# couldn't find oid; what's the real explanation for this?
# couldn't find oid; what's the real explanation for this?
raise
UndoError
(
"_loadBack() failed for %s"
,
oid
)
raise
UndoError
(
"_loadBack() failed for %s"
,
oid
)
try
:
try
:
data
=
self
.
tryToResolveConflict
(
oid
,
ctid
,
tid
,
bdata
,
cdata
)
data
=
self
.
tryToResolveConflict
(
oid
,
ctid
,
tid
,
pre_data
,
current_data
)
return
data
,
0
,
ipos
return
data
,
0
,
ipos
except
ConflictError
:
except
ConflictError
:
pass
pass
...
@@ -1002,7 +1025,8 @@ class FileStorage(
...
@@ -1002,7 +1025,8 @@ class FileStorage(
# We're undoing a blob modification operation.
# We're undoing a blob modification operation.
# We have to copy the blob data
# We have to copy the blob data
tmp
=
mktemp
(
dir
=
self
.
fshelper
.
temp_dir
)
tmp
=
mktemp
(
dir
=
self
.
fshelper
.
temp_dir
)
with
self
.
openCommittedBlobFile
(
h
.
oid
,
userial
)
as
sfp
:
with
self
.
openCommittedBlobFile
(
h
.
oid
,
userial
)
as
sfp
:
with
open
(
tmp
,
'wb'
)
as
dfp
:
with
open
(
tmp
,
'wb'
)
as
dfp
:
cp
(
sfp
,
dfp
)
cp
(
sfp
,
dfp
)
self
.
_blob_storeblob
(
h
.
oid
,
self
.
_tid
,
tmp
)
self
.
_blob_storeblob
(
h
.
oid
,
self
.
_tid
,
tmp
)
...
@@ -1237,7 +1261,8 @@ class FileStorage(
...
@@ -1237,7 +1261,8 @@ class FileStorage(
continue
continue
if
len
(
line
)
!=
16
:
if
len
(
line
)
!=
16
:
raise
ValueError
(
"Bad record in "
,
self
.
blob_dir
,
'.removed'
)
raise
ValueError
(
"Bad record in "
,
self
.
blob_dir
,
'.removed'
)
oid
,
tid
=
line
[:
8
],
line
[
8
:]
oid
,
tid
=
line
[:
8
],
line
[
8
:]
path
=
fshelper
.
getBlobFilename
(
oid
,
tid
)
path
=
fshelper
.
getBlobFilename
(
oid
,
tid
)
...
...
src/ZODB/tests/TransactionalUndoStorage.py
View file @
75bae1a6
...
@@ -801,3 +801,25 @@ class TransactionalUndoStorage:
...
@@ -801,3 +801,25 @@ class TransactionalUndoStorage:
def
checkIndicesInUndoLog
(
self
):
def
checkIndicesInUndoLog
(
self
):
self
.
_exercise_info_indices
(
"undoLog"
)
self
.
_exercise_info_indices
(
"undoLog"
)
def
checkUndoMultipleConflictResolution
(
self
,
reverse
=
False
):
from
.ConflictResolution
import
PCounter
db
=
DB
(
self
.
_storage
)
with
db
.
transaction
()
as
conn
:
conn
.
root
.
x
=
PCounter
()
for
i
in
range
(
4
):
with
db
.
transaction
()
as
conn
:
conn
.
transaction_manager
.
get
().
note
(
str
(
i
))
conn
.
root
.
x
.
inc
()
ids
=
[
l
[
'id'
]
for
l
in
db
.
undoLog
(
1
,
3
)]
if
reverse
:
ids
=
list
(
reversed
(
ids
))
db
.
undoMultiple
(
ids
)
transaction
.
commit
()
def
checkUndoMultipleConflictResolutionReversed
(
self
):
self
.
checkUndoMultipleConflictResolution
(
True
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment