diff --git a/product/ZSQLCatalog/SQLCatalog.py b/product/ZSQLCatalog/SQLCatalog.py index c12eb8e3a0f3d92bd28f1adada958e711c3dbb0e..d108e46ace8883a09ee001d7fd5ec9d465dc082f 100755 --- a/product/ZSQLCatalog/SQLCatalog.py +++ b/product/ZSQLCatalog/SQLCatalog.py @@ -18,10 +18,12 @@ import ExtensionClass import Globals from Globals import DTMLFile, PersistentMapping from string import lower, split, join -from thread import get_ident +from thread import allocate_lock from OFS.Folder import Folder from AccessControl import ClassSecurityInfo, getSecurityManager from BTrees.OIBTree import OIBTree +from App.config import getConfiguration +from BTrees.Length import Length from DateTime import DateTime from Products.PluginIndexes.common.randid import randid @@ -35,6 +37,8 @@ import string from cStringIO import StringIO from xml.dom.minidom import parse, parseString, getDOMImplementation from xml.sax.saxutils import escape, quoteattr +import os +import md5 try: from Products.PageTemplates.Expressions import SecureModuleImporter @@ -178,6 +182,11 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): 'type' : 'selection', 'select_variable' : 'getCatalogMethodIds', 'mode' : 'w' }, + { 'id' : 'sql_catalog_reserve_uid', + 'description' : 'A method to reserve a uid value', + 'type' : 'selection', + 'select_variable' : 'getCatalogMethodIds', + 'mode' : 'w' }, { 'id' : 'sql_catalog_object', 'description' : 'Methods to be called to catalog an object', 'type' : 'multiple selection', @@ -306,6 +315,7 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): sql_catalog_produce_reserved = 'z_produce_reserved_uid_list' sql_catalog_clear_reserved = 'z_clear_reserved' + sql_catalog_reserve_uid = 'z_reserve_uid' sql_catalog_object = ('z_catalog_object',) sql_uncatalog_object = ('z_uncatalog_object',) sql_update_object = ('z_update_object',) @@ -332,7 +342,20 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): sql_catalog_multivalue_keys = () sql_catalog_related_keys = () - _after_clear_reserved = 0 + # These are ZODB variables, so shared by multiple Zope instances. + # This is set to the last logical time when clearReserved is called. + _last_clear_reserved_time = 0 + # This is to record the maximum value of uids. Because this uses the class Length + # in BTrees.Length, this does not generate conflict errors. + _max_uid = None + + # These are class variable on memory, so shared only by threads in the same Zope instance. + # This is set to the time when reserved uids are cleared in this Zope instance. + _local_clear_reserved_time = None + # This is used for exclusive access to the list of reserved uids. + _reserved_uid_lock = allocate_lock() + # This is an instance id which specifies who owns which reserved uids. + _instance_id = getattr(getConfiguration(), 'instance_id', None) manage_catalogView = DTMLFile('dtml/catalogView',globals()) manage_catalogFilter = DTMLFile('dtml/catalogFilter',globals()) @@ -457,7 +480,7 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): """ methods = self.sql_clear_catalog for method_name in methods: - method = getattr(self,method_name) + method = getattr(self, method_name) try: method() except: @@ -465,7 +488,14 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): pass # Reserved uids have been removed. - self._after_clear_reserved = 1 + self.clearReserved() + + # Add a dummy item so that SQLCatalog will not use existing uids again. + if self._max_uid is not None and self._max_uid() != 0: + method_id = self.sql_catalog_reserve_uid + method = getattr(self, method_id) + self._max_uid.change(1) + method(uid = self._max_uid()) # Remove the cache of catalog schema. if hasattr(self, '_v_catalog_schema_dict') : @@ -480,7 +510,7 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): method_id = self.sql_catalog_clear_reserved method = getattr(self, method_id) method() - self._after_clear_reserved = 1 + self._last_clear_reserved_time += 1 def __getitem__(self, uid): """ @@ -621,17 +651,29 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): """ Produces reserved uids in advance """ - method_id = self.sql_catalog_produce_reserved - method = getattr(self, method_id) - thread_id = get_ident() + klass = self.__class__ + assert klass._reserved_uid_lock.locked() uid_list = getattr(self, '_v_uid_buffer', []) - if self._after_clear_reserved: - # Reset uid list after clear reserved - self._after_clear_reserved = 0 + # This checks if the list of local reserved uids was cleared after clearReserved + # had been called. + if klass._local_clear_reserved_time != self._last_clear_reserved_time: uid_list = [] - if len(uid_list) < UID_BUFFER_SIZE: - date = DateTime() - new_uid_list = method(count = UID_BUFFER_SIZE, thread_id=thread_id, date=date) + klass._local_clear_reserved_time = self._last_clear_reserved_time + if len(uid_list) == 0: + method_id = self.sql_catalog_produce_reserved + method = getattr(self, method_id) + instance_id = klass._instance_id + if instance_id is None: + # Generate an instance id randomly. Note that there is a small possibility that this + # would conflict with others. + random_factor_list = [time.time(), os.getpid(), os.times()] + try: + random_factor_list.append(os.getloadavg()) + except OSError: + pass + instance_id = md5.new(str(random_factor_list)).hexdigest()[:30] + klass._instance_id = instance_id + new_uid_list = method(count = UID_BUFFER_SIZE, instance_id=instance_id) uid_list.extend( filter(lambda x: x != 0, map(lambda x: x.uid, new_uid_list ))) self._v_uid_buffer = uid_list @@ -654,12 +696,22 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): Similar problems may happen with relations and acquisition of uid values (ex. order_uid) with the risk of graph loops """ - self.produceUid() - uid_list = getattr(self, '_v_uid_buffer', []) - if len(uid_list) > 0: - return uid_list.pop() - else: - raise CatalogError("Could not retrieve new uid") + klass = self.__class__ + try: + klass._reserved_uid_lock.acquire() + self.produceUid() + uid_list = getattr(self, '_v_uid_buffer', []) + if len(uid_list) > 0: + uid = uid_list.pop() + if self._max_uid is None: + self._max_uid = Length() + if uid > self._max_uid(): + self._max_uid.set(uid) + return uid + else: + raise CatalogError("Could not retrieve new uid") + finally: + klass._reserved_uid_lock.release() def manage_catalogObject(self, REQUEST, RESPONSE, URL1, urls=None): """ index Zope object(s) that 'urls' point to """ @@ -780,6 +832,10 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): # Try to use existing uid # WARNING COPY PASTE.... uid = object.uid + if uid < 0: + LOG('SQLCatalog Warning:', 0, 'The uid of %r has been removed, because it had a negative value %d' % (object, uid)) + object.uid = 0 + uid = 0 else: # Look up in (previous) path uid = 0 @@ -787,6 +843,12 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): index = uid # We trust the current uid else: index = self.getUidForPath(path) + try: + index = int(index) + except TypeError: + pass + if index is not None and index < 0: + raise CatalogError, 'A negative uid %d is used for %s. Your catalog is broken. Recreate your catalog.' % (index, path) if index: if (uid != index): # The new database must not change the uid. @@ -795,8 +857,9 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): self.catalog_object(object, path, is_object_moved=is_object_moved, sql_catalog_id=self.source_sql_catalog_id) return self.catalogObject(object, path, is_object_moved=is_object_moved) + LOG('SQLCatalog Warning: uid of %r changed from %r to %r !!! This can be fatal. You should reindex the whole site immediately.' % (object, uid, index)) # Update uid attribute of object - uid = int(index) + uid = index #LOG("Write Uid",0, "uid %s index %s" % (uid, index)) object.uid = uid # We will check if there is an filter on this @@ -865,18 +928,23 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): #LOG('catalogObject', 0, 'uid = %r, catalog_path = %r' % (uid, catalog_path)) if catalog_path == "reserved": # Reserved line in catalog table - uid_list = getattr(aq_base(self), '_v_uid_buffer', []) - if uid in uid_list: - # This is the case where: - # 1. The object got an uid. - # 2. The catalog was cleared. - # 3. The catalog produced the same reserved uid. - # 4. The object was reindexed. - # In this case, the uid is not reserved any longer, but - # SQLCatalog believes that it is still reserved. So it is - # necessary to remove the uid from the list explicitly. - uid_list.remove(uid) - self._v_uid_buffer = uid_list + klass = self.__class__ + try: + klass._reserved_uid_lock.acquire() + uid_list = getattr(aq_base(self), '_v_uid_buffer', []) + if uid in uid_list: + # This is the case where: + # 1. The object got an uid. + # 2. The catalog was cleared. + # 3. The catalog produced the same reserved uid. + # 4. The object was reindexed. + # In this case, the uid is not reserved any longer, but + # SQLCatalog believes that it is still reserved. So it is + # necessary to remove the uid from the list explicitly. + uid_list.remove(uid) + self._v_uid_buffer = uid_list + finally: + klass._reserved_uid_lock.release() insert_catalog_line = 0 insert_line = 1 #LOG("SQLCatalog Warning: insert_catalog_line, case2",0,insert_catalog_line) @@ -986,7 +1054,7 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): try: object._setUid(self.newUid()) except: - raise RuntimeError, 'could not set missing uid fro %r' % (object,) + raise RuntimeError, 'could not set missing uid for %r' % (object,) methods = self.sql_catalog_object_list for method_name in methods: @@ -1334,7 +1402,7 @@ class Catalog(Folder, Persistent, Acquisition.Implicit, ExtensionClass.Base): sort_index = join(new_sort_index,',') sort_on = str(sort_index) except: - LOG('SQLCatalog.buildSQLQuery',0,'WARNING, Unable to build the new sort index') + LOG('SQLCatalog.buildSQLQuery',0,'WARNING, Unable to build the new sort index', error=sys.exc_info()) pass # Rebuild keywords to behave as new style query (_usage='toto:titi' becomes {'toto':'titi'})