[Zope-Checkins] CVS: Zope2 - ConflictResolution.py:1.1.4.1 BaseStorage.py:1.10.54.1 Connection.py:1.43.2.3 FileStorage.py:1.49.8.2

Jim Fulton jim@digiciool.com
Thu, 15 Mar 2001 08:10:37 -0500 (EST)


Update of /cvs-repository/Zope2/lib/python/ZODB
In directory korak:/home/jim/atmp/merge/2.3/lib/python/ZODB

Modified Files:
      Tag: zope-2_3-branch
	BaseStorage.py Connection.py FileStorage.py 
Added Files:
      Tag: zope-2_3-branch
	ConflictResolution.py 
Log Message:
Merged changes from Catalog-BTrees-Integration branch.


--- Added File ConflictResolution.py in package Zope2 ---
##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################
from cStringIO import StringIO
from cPickle import Unpickler, Pickler
import sys

#import traceback

bad_classes={}
bad_class=bad_classes.has_key

ResolvedSerial='rs'

def _classFactory(location, name,
                  _silly=('__doc__',), _globals={}):
    return getattr(__import__(location, _globals, _globals, _silly),
                   name)

def state(self, oid, serial, prfactory):
    p=self.loadSerial(oid, serial)
    file=StringIO(p)
    unpickler=Unpickler(file)
    unpickler.persistent_load=prfactory
    class_tuple=unpickler.load()
    state=unpickler.load()
    return state


class PersistentReference:

    def __repr__(self):
        return "PR(%s %s)" % (id(self), self.data)

    def __getstate__(self):
        raise "Can't pickle PersistentReference"

class PersistentReferenceFactory:

    data=None
    
    def __call__(self, oid,
                 getattr=getattr, None=None):

        data=self.data
        if not data: data=self.data={}

        r=data.get(oid, None)
        if r is None:
            r=PersistentReference()
            r.data=oid
            data[oid]=r

        return r

def persistent_id(object,
                  PersistentReference=PersistentReference,
                  getattr=getattr
                  ):
    if getattr(object, '__class__', 0) is not PersistentReference:
        return None
    return object.data

class ConflictResolvingStorage:
    "Mix-in class that provides conflict resolution handling for storages"

    def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle):
        #class_tuple, old, committed, newstate = ('',''), 0, 0, 0
        try:
            file=StringIO(newpickle)
            unpickler=Unpickler(file)
            prfactory=PersistentReferenceFactory()
            unpickler.persistent_load=prfactory
            class_tuple=unpickler.load()[0]
            if bad_class(class_tuple):
                #sys.stderr.write(' b%s ' % class_tuple[1]); sys.stderr.flush()
                return 0
            
            newstate=unpickler.load()
            klass=_classFactory(class_tuple[0], class_tuple[1])
            klass._p_resolveConflict                    
            inst=klass.__basicnew__()
            
            try:
                resolve=inst._p_resolveConflict
            except AttributeError:
                bad_classes[class_tuple]=1
                #traceback.print_exc()
                #sys.stderr.write(' b%s ' % class_tuple[1]); sys.stderr.flush()
                return 0
            
            old=state(self, oid, oldSerial, prfactory)
            committed=state(self, oid, committedSerial, prfactory)

            resolved=resolve(old, committed, newstate)
                
            file=StringIO()
            pickler=Pickler(file,1)
            pickler.persistent_id=persistent_id
            pickler.dump(class_tuple)
            pickler.dump(resolved)
            #sys.stderr.write(' r%s ' % class_tuple[1]); sys.stderr.flush()
            return file.getvalue(1)
        except Exception, v:
            #print '='*70
            #print v, v.args
            #print '='*70
            #print old
            #print '='*70
            #print committed
            #print '='*70
            #print newstate
            #print '='*70
            
            #traceback.print_exc()

            #sys.stderr.write(' c%s ' % class_tuple[1]); sys.stderr.flush()

            return 0


--- Updated File BaseStorage.py in package Zope2 --
--- BaseStorage.py	2000/08/07 20:42:28	1.10
+++ BaseStorage.py	2001/03/15 13:10:35	1.10.54.1
@@ -86,7 +86,8 @@
 """
 __version__='$Revision$'[11:-2]
 
-import time, bpthread, UndoLogCompatible
+import ThreadLock, bpthread
+import time, UndoLogCompatible
 from POSException import UndoError
 from TimeStamp import TimeStamp
 z64='\0'*8
@@ -101,7 +102,7 @@
         self.__name__=name
 
         # Allocate locks:
-        l=bpthread.allocate_lock()
+        l=ThreadLock.allocate_lock()
         self._lock_acquire=l.acquire
         self._lock_release=l.release
         l=bpthread.allocate_lock()

--- Updated File Connection.py in package Zope2 --
--- Connection.py	2001/02/09 14:08:27	1.43.2.2
+++ Connection.py	2001/03/15 13:10:35	1.43.2.3
@@ -94,8 +94,9 @@
 from ExtensionClass import Base
 from time import time
 import Transaction, string, ExportImport, sys, traceback, TmpStore
-from zLOG import LOG, ERROR
+from zLOG import LOG, ERROR, BLATHER
 from coptimizations import new_persistent_id
+from ConflictResolution import ResolvedSerial
 
 ExtensionKlass=Base.__class__
 
@@ -230,7 +231,10 @@
 
         This just deactivates the thing.
         """
-        self._cache.invalidate(object._p_oid)
+        if object is self:
+            self._cache.invalidate(self._invalidated)
+        else:
+            self._cache.invalidate(object._p_oid)
 
     def cacheFullSweep(self, dt=0): self._cache.full_sweep(dt)
     def cacheMinimize(self, dt=0): self._cache.minimize(dt)
@@ -257,6 +261,8 @@
         db._closeConnection(self)
                         
     def commit(self, object, transaction, _type=type, _st=type('')):
+        if object is self:
+            return # we registered ourself  
         oid=object._p_oid
         invalid=self._invalid
         if oid is None or object._p_jar is not self:
@@ -267,7 +273,12 @@
             self._creating.append(oid)
 
         elif object._p_changed:
-            if invalid(oid) or invalid(None): raise ConflictError, `oid`
+            if (
+                (invalid(oid) and not hasattr(object, '_p_resolveConflict'))
+                or
+                invalid(None)
+                ):
+                raise ConflictError, `oid`
             self._invalidating.append(oid)
 
         else:
@@ -328,7 +339,13 @@
                 self._creating.append(oid)
             else:
                 #XXX We should never get here
-                if invalid(oid) or invalid(None): raise ConflictError, `oid`
+                if (
+                    (invalid(oid) and
+                     not hasattr(object, '_p_resolveConflict'))
+                    or
+                    invalid(None)
+                    ):
+                    raise ConflictError, `oid`
                 self._invalidating.append(oid)
                 
             klass = object.__class__
@@ -362,8 +379,12 @@
                 # Note that if s is false, then the storage defered the return
                 if _type(s) is _st:
                     # normal case
-                    object._p_serial=s
-                    object._p_changed=0
+                    if s is ResolvedSerial:
+                        # resolved conflict
+                        object._p_changed=None
+                    else:
+                        object._p_serial=s
+                        object._p_changed=0
                 else:
                     # defered returns
                     for oi, s in s:
@@ -389,6 +410,10 @@
         tmp=self._tmp
         if tmp is _None: return
         src=self._storage
+
+        LOG('ZODB', BLATHER,
+            'Commiting subtransaction of size %s' % src.getSize())
+        
         self._storage=tmp
         self._tmp=_None
 
@@ -487,7 +512,13 @@
             # notifications between the time we check and the time we
             # read.
             invalid=self._invalid
-            if invalid(oid) or invalid(None): raise ConflictError, `oid`
+            if invalid(oid) or invalid(None):
+                if not hasattr(object.__class__, '_p_independent'):
+                    get_transaction().register(self)
+                    raise ConflictError(`oid`, `object.__class__`)
+                invalid=1
+            else:
+                invalid=0
 
             file=StringIO(p)
             unpickler=Unpickler(file)
@@ -502,6 +533,14 @@
                 for k,v in state.items(): d[k]=v
 
             object._p_serial=serial
+
+            if invalid:
+                if object._p_independent():
+                    try: del self._invalidated[oid]
+                    except KeyError: pass
+                else:
+                    get_transaction().register(self)
+                    raise ConflictError(`oid`, `object.__class__`)
 
         except:
             t, v =sys.exc_info()[:2]

--- Updated File FileStorage.py in package Zope2 --
--- FileStorage.py	2001/02/09 01:53:39	1.49.8.1
+++ FileStorage.py	2001/03/15 13:10:36	1.49.8.2
@@ -197,6 +197,7 @@
 register_subsystem('ZODB FS')
 import BaseStorage
 from cPickle import Pickler, Unpickler
+import ConflictResolution
 
 try: from posix import fsync
 except: fsync=None
@@ -240,7 +241,8 @@
 
 packed_version='FS21'
 
-class FileStorage(BaseStorage.BaseStorage):
+class FileStorage(BaseStorage.BaseStorage,
+                  ConflictResolution.ConflictResolvingStorage):
     _packt=z64
 
     def __init__(self, file_name, create=0, read_only=0, stop=None,
@@ -663,18 +665,23 @@
                         raise POSException.VersionLockError, (
                             `oid`, locked_version)
 
-                if serial != oserial: raise POSException.ConflictError, (
-                    serial, oserial)
-
+                if serial != oserial:
+                    data=self.tryToResolveConflict(oid, oserial, serial, data)
+                    if not data:
+                        raise POSException.ConflictError, (
+                            serial, oserial)
+            else:
+                oserial=serial
+                    
             tfile=self._tfile
             write=tfile.write
             pos=self._pos
             here=pos+(tfile.tell()+self._thl)
             self._tappend((oid, here))
-            serial=self._serial
+            newserial=self._serial
             write(pack(">8s8s8s8sH8s",
-                       oid,serial,p64(old),p64(pos),
-                       len(version),p64(len(data))
+                       oid, newserial, p64(old), p64(pos),
+                       len(version), p64(len(data))
                        )
                   )
             if version:
@@ -695,7 +702,8 @@
                 raise FileStorageQuotaError, (
                     'The storage quota has been exceeded.')
 
-            return serial
+            return (serial == oserial and newserial
+                    or ConflictResolution.ResolvedSerial)
         
         finally:
             self._lock_release()