products/CPSUid

changeset 32:e6c0caf8b3da

#1789: In case of conflicting transactions, id unicity was broken
author gracinet
date Tue, 28 Nov 2006 15:32:04 +0000
parents 1d002c50a609
children 5a12b7b20aa1
files CHANGES uidcounter.py
diffstat 2 files changed, 42 insertions(+), 23 deletions(-) [+]
line diff
     1.1 --- a/CHANGES
     1.2 +++ b/CHANGES
     1.3 @@ -6,7 +6,7 @@
     1.4  -
     1.5  Bug fixes
     1.6  ~~~~~~~~~
     1.7 --
     1.8 +- #1789: In case of conflicting transactions, id unicity was broken
     1.9  New internal features
    1.10  ~~~~~~~~~~~~~~~~~~~~~
    1.11  - 
     2.1 --- a/uidcounter.py
     2.2 +++ b/uidcounter.py
     2.3 @@ -1,6 +1,7 @@
     2.4  # (C) Copyright 2006 Nuxeo SAS <http://nuxeo.com>
     2.5  # Authors:
     2.6  # - Anahide Tchertchian <at@nuxeo.com>
     2.7 +# - Florent Guillaume <fg@nuxeo.com>
     2.8  #
     2.9  # This program is free software; you can redistribute it and/or modify
    2.10  # it under the terms of the GNU General Public License version 2 as published
    2.11 @@ -20,16 +21,21 @@
    2.12  
    2.13  A counter holds criteria and an integer counter applying to these criteria
    2.14  """
    2.15 +import logging
    2.16  
    2.17  from zope.interface import implements
    2.18 +from transaction import TransactionManager
    2.19  
    2.20  from Globals import InitializeClass
    2.21  from AccessControl import ClassSecurityInfo
    2.22 +from ZODB.POSException import ConflictError
    2.23  
    2.24  from Products.CMFCore.utils import SimpleItemWithProperties
    2.25  
    2.26  from Products.CPSUid.interfaces import IUidCounter
    2.27  
    2.28 +logger = logging.getLogger('CPSUid.uidcounter')
    2.29 +
    2.30  class UidCounter(SimpleItemWithProperties):
    2.31      """Uid Counter
    2.32  
    2.33 @@ -56,6 +62,8 @@
    2.34           'label': 'Criteria mapping'},
    2.35          )
    2.36  
    2.37 +    max_tries = 5
    2.38 +
    2.39      #
    2.40      # API
    2.41      #
    2.42 @@ -73,19 +81,42 @@
    2.43  
    2.44      security.declarePrivate('hit')
    2.45      def hit(self):
    2.46 -        """Hit the counter: return current counter and increment its value
    2.47 +        """Hit the counter: return current counter and increment its value.
    2.48  
    2.49 -        XXXXXX GR. Currently the conflict resolving system breaks the
    2.50 -        counter unicity because two conflicting threads will get the same
    2.51 -        value and the transaction will not be replayed.
    2.52 -        We should make an independent micro-transaction here with its own
    2.53 -        ZODB connection and implement a replay system like the publisher's.
    2.54 +        Fires a dedicated micro transaction to diminish risks of conflicts
    2.55 +        because we definitely *don't* want to resolve them (that breaks
    2.56 +        returned values' unicity).
    2.57 +
    2.58          """
    2.59 -        # XXX AT: this is were conflict issues should be handled (?)
    2.60 -        counter_value = self.counter_current
    2.61 -        self.counter_current = counter_value + 1
    2.62 -        return counter_value
    2.63 +        db = self._p_jar.db()
    2.64  
    2.65 +        retries = self.max_tries
    2.66 +        while retries:
    2.67 +            try:
    2.68 +                logger.debug(
    2.69 +                    "Hitting on counter %s, trying %d times", self, retries)
    2.70 +                tm = TransactionManager()
    2.71 +
    2.72 +                cnx = db.open(transaction_manager=tm)
    2.73 +                tm.begin()
    2.74 +                ob = cnx.get(self._p_oid)
    2.75 +
    2.76 +                v = ob.counter_current
    2.77 +                ob.counter_current = v + 1
    2.78 +
    2.79 +                tm.commit()
    2.80 +                cnx.close()
    2.81 +            except ConflictError:
    2.82 +                logger.debug('ConflictError')
    2.83 +                tm.abort()
    2.84 +                cnx.close()
    2.85 +                retries -= 1
    2.86 +            else:
    2.87 +                break
    2.88 +        else:
    2.89 +            # exhausted all tries
    2.90 +            raise ConflictError
    2.91 +        return v
    2.92  
    2.93      security.declarePrivate('reset')
    2.94      def reset(self):
    2.95 @@ -105,16 +136,4 @@
    2.96                  criteria_dict[key] = value
    2.97          return criteria_dict
    2.98  
    2.99 -    # avoid conflicts on counter current value
   2.100 -    def _p_resolveConflict(self, oldState, savedState, newState):
   2.101 -        """Avoid conflicts when changing the counter value
   2.102 -        """
   2.103 -        # XXX GR: this does resolve conflicts, but the two callers got the same
   2.104 -        # number, hence really need to randomize the uid to enforce unicity
   2.105 -        bigger_counter = max(savedState['counter_current'],
   2.106 -                             newState['counter_current'])
   2.107 -        newState['counter_current'] = bigger_counter + 1
   2.108 -        return newState
   2.109 -
   2.110 -
   2.111  InitializeClass(UidCounter)