I've been paying some attention to ConflictErrors lately. There are
a couple of problems right now in some packages we rely on. This is
a heads up of the problems, and a chance to give me feedback on the
solutions, particularly in regards to how I should distribute them.
1) zope.app.keyreference.persistent.Persistent references will often
defeat conflict resolution in containers that use their __cmp__
within conflict resolution--for instance, the various BTree
collections. The keyreferences need to access the Persistent objects
they wrap in order to compare, and during conflict resolution of
containers like the BTrees, the persistent objects will not be
available (Placeholder objects).
General solution: the keyreference objects need to stash the database
name and oid on themselves, so they can compare without access to the
persistent object they wrap. It's duplicating information, but it
should be reliable, and fits within the constraints of the current
conflict resolution system.
The complication is legacy databases. What should they do with
keyreferences that do not have the stashed information? I propose
two competing solutions. I favor the first. A constraint is that I
do not want write on read.
a) change the current implementation of
zope.app.keyreference.persistent.Persistent so that new attribute
`_identifier` is a data descriptor that returns the persistent
object's database name and oid. In the __init__, stash these values
in the __dict__, overriding the data descriptor. __cmp__ would use
_identifier rather than the persistent object directly. Future
instances will behave well with conflict resolution, and old
instances will still work as they do now. Converting old instances
to work well with conflict resolution means a generations script that
finds your old keyreferences and writes the _identifier in the __dict__.
b) write another adapter for Persistent objects in another package
(zc.keyreference or some such) that has the new behavior. Old
instances have the old class and behavior, new ones have the new
behavior. Converting old instances means a generations script
finding the old instances and replacing them with the new: a bit
2) The second problem is less serious, but still evidenced in our
production apps, and representative of a class of problems I've
wanted to address for awhile. zope.app.session writes to a session
if it is accessed
seconds after a session data was last updated, to indicate that the
session is still active. It scribbles time.time(). The default
resolution is 50 minutes.
Given a page that makes several requests, it is very easy (speaking
as an observer of server logs) to generate conflict errors in a
modern browser that parallelizes requests for resources on a page,
once the time comes to update.
I'd like to make a module that contains two very simple classes,
similar to BTrees.Length, that are persistent objects that hold a
single value, and have simple resolution policies. One would always
prefer the greater or equal value of the two new values, and the
other would prefer the lesser or equal value of the two new options.
The session code, as well as other similar expiration-based use
cases, could use these classes to store the time.time() float--the
session code would use the max variant.
Since sessions are transient, I probably wouldn't worry too much
about legacy databases' session data. The question would be where to
put the new shared classes, and whether to make zope.app.session
depend on the new classes.
The solution that requires the least agreement is for me to create a
new package (zc.minmax or something like that) with the simple
objects, and another new package (zc.session) that extends
zope.app.session.session to rely on the package.
The solution that requires the most agreement is to put the new
classes in the ZODB somewhere (where?) and make zope.app.session rely
on the new classes. I don't have a clear vision on where to put
these classes in the ZODB, and the ZODB is in beta, so I'm inclined
more towards the first, standalone solution, even though I don't love
it: the changes I'm making seem pretty much of general value.
I suppose a compromise would be to make a zope.minmax, and then have
zope.app.session depend on it. That would be fine by me, but making
a zope.* package requires agreement from interested parties.
Thoughts are welcome. I'll hope to do something about this tomorrow
or Friday. Of course, another factor is that Zope is in the middle
of a release cycle. The first problem in particular seems serious
enough to me to warrant a fix in the release, but I'm not opposed to
making a bunch of zc.* packages for this and move on, even if it
feels a bit silly.
Zope3-dev mailing list