示例#1
0
    def _setstate(self, obj):
        # Helper for setstate(), which provides logging of failures.

        # The control flow is complicated here to avoid loading an
        # object revision that we are sure we aren't going to use.  As
        # a result, invalidation tests occur before and after the
        # load.  We can only be sure about invalidations after the
        # load.

        # If an object has been invalidated, there are several cases
        # to consider:
        # 1. Check _p_independent()
        # 2. Try MVCC
        # 3. Raise ConflictError.

        # Does anything actually use _p_independent()?  It would simplify
        # the code if we could drop support for it.
        # (BTrees.Length does.)

        # There is a harmless data race with self._invalidated.  A
        # dict update could go on in another thread, but we don't care
        # because we have to check again after the load anyway.

        if self._invalidatedCache:
            raise ReadConflictError()

        if (obj._p_oid in self._invalidated
                and not myhasattr(obj, "_p_independent")):
            # If the object has _p_independent(), we will handle it below.
            self._load_before_or_conflict(obj)
            return

        p, serial = self._storage.load(obj._p_oid, self._version)
        self._load_count += 1

        self._inv_lock.acquire()
        try:
            invalid = obj._p_oid in self._invalidated
        finally:
            self._inv_lock.release()

        if invalid:
            if myhasattr(obj, "_p_independent"):
                # This call will raise a ReadConflictError if something
                # goes wrong
                self._handle_independent(obj)
            else:
                self._load_before_or_conflict(obj)
                return

        self._reader.setGhostState(obj, p)
        obj._p_serial = serial
        self._cache.update_object_size_estimation(obj._p_oid, len(p))
        obj._p_estimated_size = len(p)

        # Blob support
        if isinstance(obj, Blob):
            obj._p_blob_uncommitted = None
            obj._p_blob_committed = self._storage.loadBlob(obj._p_oid, serial)
示例#2
0
    def _commit(self, transaction):
        """Commit changes to an object"""

        if self._import:
            # We are importing an export file. We alsways do this
            # while making a savepoint so we can copy export data
            # directly to our storage, typically a TmpStore.
            self._importDuringCommit(transaction, *self._import)
            self._import = None

        # Just in case an object is added as a side-effect of storing
        # a modified object.  If, for example, a __getstate__() method
        # calls add(), the newly added objects will show up in
        # _added_during_commit.  This sounds insane, but has actually
        # happened.

        self._added_during_commit = []

        if self._invalidatedCache:
            raise ConflictError()

        for obj in self._registered_objects:
            oid = obj._p_oid
            assert oid
            if oid in self._conflicts:
                raise ReadConflictError(object=obj)

            if obj._p_jar is not self:
                raise InvalidObjectReference(obj, obj._p_jar)
            elif oid in self._added:
                assert obj._p_serial == z64
            elif obj._p_changed:
                if oid in self._invalidated:
                    resolve = getattr(obj, "_p_resolveConflict", None)
                    if resolve is None:
                        raise ConflictError(object=obj)
                self._modified.append(oid)
            else:
                # Nothing to do.  It's been said that it's legal, e.g., for
                # an object to set _p_changed to false after it's been
                # changed and registered.
                continue

            self._store_objects(ObjectWriter(obj), transaction)

        for obj in self._added_during_commit:
            self._store_objects(ObjectWriter(obj), transaction)
        self._added_during_commit = None
示例#3
0
    def _handle_independent(self, obj):
        # Helper method for setstate() handles possibly independent objects
        # Call _p_independent(), if it returns True, setstate() wins.
        # Otherwise, raise a ConflictError.

        if obj._p_independent():
            self._inv_lock.acquire()
            try:
                try:
                    self._invalidated.remove(obj._p_oid)
                except KeyError:
                    pass
            finally:
                self._inv_lock.release()
        else:
            self._conflicts[obj._p_oid] = 1
            self._register(obj)
            raise ReadConflictError(object=obj)
示例#4
0
    def _check_tid_after_load(self,
                              oid_int,
                              actual_tid_int,
                              expect_tid_int=None):
        """Verify the tid of an object loaded from the database is sane."""
        if actual_tid_int > self.current_tid:
            # Strangely, the database just gave us data from a future
            # transaction. We can't give the data to ZODB because that
            # would be a consistency violation. However, the cause is
            # hard to track down, so issue a ReadConflictError and
            # hope that the application retries successfully.
            msg = ("Got data for OID 0x%(oid_int)x from "
                   "future transaction %(actual_tid_int)d (%(got_ts)s).  "
                   "Current transaction is %(current_tid)d (%(current_ts)s)." %
                   {
                       'oid_int': oid_int,
                       'actual_tid_int': actual_tid_int,
                       'current_tid': self.current_tid,
                       'got_ts': str(TimeStamp(p64(actual_tid_int))),
                       'current_ts': str(TimeStamp(p64(self.current_tid))),
                   })
            raise ReadConflictError(msg)

        if expect_tid_int is not None and actual_tid_int != expect_tid_int:
            # Uh-oh, the cache is inconsistent with the database.
            # We didn't get a TID from the future, but it's not what we
            # had in our delta_after0 map, which means...we missed a change
            # somewhere.
            #
            # Possible causes:
            #
            # - The database MUST provide a snapshot view for each
            #   session; this error can occur if that requirement is
            #   violated. For example, MySQL's MyISAM engine is not
            #   sufficient for the object_state table because MyISAM
            #   can not provide a snapshot view. (InnoDB is
            #   sufficient.)
            #
            # - (Similar to the last one.) Using too low of a
            #   isolation level for the database connection and
            #   viewing unrelated data.
            #
            # - Something could be writing to the database out
            #   of order, such as a version of RelStorage that
            #   acquires a different commit lock.
            #
            # - A software bug. In the past, there was a subtle bug
            #   in after_poll() that caused it to ignore the
            #   transaction order, leading it to sometimes put the
            #   wrong tid in delta_after*.
            #
            # - Restarting a load connection at a future point we hadn't
            #   actually polled to, such that our current_tid is out of sync
            #   with the connection's *actual* viewable tid?
            cp0, cp1 = self.checkpoints

            msg = ("Detected an inconsistency "
                   "between the RelStorage cache and the database "
                   "while loading an object using the delta_after0 dict.  "
                   "Please verify the database is configured for "
                   "ACID compliance and that all clients are using "
                   "the same commit lock.  "
                   "(oid_int=%(oid_int)r, expect_tid_int=%(expect_tid_int)r, "
                   "actual_tid_int=%(actual_tid_int)r, "
                   "current_tid=%(current_tid)r, cp0=%(cp0)r, cp1=%(cp1)r, "
                   "len(delta_after0)=%(lda0)r, len(delta_after1)=%(lda1)r, "
                   "pid=%(pid)r, thread_ident=%(thread_ident)r)" % {
                       'oid_int': oid_int,
                       'expect_tid_int': expect_tid_int,
                       'actual_tid_int': actual_tid_int,
                       'current_tid': self.current_tid,
                       'cp0': cp0,
                       'cp1': cp1,
                       'lda0': len(self.delta_after0),
                       'lda1': len(self.delta_after1),
                       'pid': os.getpid(),
                       'thread_ident': threading.current_thread(),
                   })
            # We reset ourself as if we hadn't polled, and hope the transient
            # error gets retried in a working, consistent view.
            self._reset(msg)
示例#5
0
    def _check_tid_after_load(self,
                              oid_int,
                              actual_tid_int,
                              expect_tid_int=None):
        """Verify the tid of an object loaded from the database is sane."""

        if actual_tid_int > self.current_tid:
            # Strangely, the database just gave us data from a future
            # transaction.  We can't give the data to ZODB because that
            # would be a consistency violation.  However, the cause is hard
            # to track down, so issue a ReadConflictError and hope that
            # the application retries successfully.
            raise ReadConflictError(
                "Got data for OID 0x%(oid_int)x from "
                "future transaction %(actual_tid_int)d (%(got_ts)s).  "
                "Current transaction is %(current_tid)d (%(current_ts)s)." % {
                    'oid_int': oid_int,
                    'actual_tid_int': actual_tid_int,
                    'current_tid': self.current_tid,
                    'got_ts': str(TimeStamp(p64(actual_tid_int))),
                    'current_ts': str(TimeStamp(p64(self.current_tid))),
                })

        if expect_tid_int is not None and actual_tid_int != expect_tid_int:
            # Uh-oh, the cache is inconsistent with the database.
            # Possible causes:
            #
            # - The database MUST provide a snapshot view for each
            #   session; this error can occur if that requirement is
            #   violated. For example, MySQL's MyISAM engine is not
            #   sufficient for the object_state table because MyISAM
            #   can not provide a snapshot view. (InnoDB is
            #   sufficient.)
            #
            # - Something could be writing to the database out
            #   of order, such as a version of RelStorage that
            #   acquires a different commit lock.
            #
            # - A software bug. In the past, there was a subtle bug
            #   in after_poll() that caused it to ignore the
            #   transaction order, leading it to sometimes put the
            #   wrong tid in delta_after*.
            cp0, cp1 = self.checkpoints
            import os
            import thread
            raise AssertionError(
                "Detected an inconsistency "
                "between the RelStorage cache and the database "
                "while loading an object using the delta_after0 dict.  "
                "Please verify the database is configured for "
                "ACID compliance and that all clients are using "
                "the same commit lock.  "
                "(oid_int=%(oid_int)r, expect_tid_int=%(expect_tid_int)r, "
                "actual_tid_int=%(actual_tid_int)r, "
                "current_tid=%(current_tid)r, cp0=%(cp0)r, cp1=%(cp1)r, "
                "len(delta_after0)=%(lda0)r, len(delta_after1)=%(lda1)r, "
                "pid=%(pid)r, thread_ident=%(thread_ident)r)" % {
                    'oid_int': oid_int,
                    'expect_tid_int': expect_tid_int,
                    'actual_tid_int': actual_tid_int,
                    'current_tid': self.current_tid,
                    'cp0': cp0,
                    'cp1': cp1,
                    'lda0': len(self.delta_after0),
                    'lda1': len(self.delta_after1),
                    'pid': os.getpid(),
                    'thread_ident': thread.get_ident(),
                })
示例#6
0
    def poll_invalidations(self, conn, cursor, prev_polled_tid, ignore_tid):
        """Polls for new transactions.

        conn and cursor must have been created previously by open_for_load().
        prev_polled_tid is the tid returned at the last poll, or None
        if this is the first poll.  If ignore_tid is not None, changes
        committed in that transaction will not be included in the list
        of changed OIDs.

        Returns (changes, new_polled_tid), where changes is either
        a list of (oid, tid) that have changed, or None to indicate
        that the changes are too complex to list.  new_polled_tid can be
        0 if there is no data in the database.
        """
        # find out the tid of the most recent transaction.
        cursor.execute(self.poll_query)
        rows = list(cursor)
        if not rows:
            # No data.
            return None, 0
        new_polled_tid = rows[0][0]
        if not new_polled_tid:
            # No data.
            return None, 0

        if prev_polled_tid is None:
            # This is the first time the connection has polled.
            return None, new_polled_tid

        if new_polled_tid == prev_polled_tid:
            # No transactions have been committed since prev_polled_tid.
            return (), new_polled_tid

        elif new_polled_tid > prev_polled_tid:
            # New transaction(s) have been added.

            if self.keep_history:
                # If the previously polled transaction no longer exists,
                # the cache is too old and needs to be cleared.
                # XXX Do we actually need to detect this condition? I think
                # if we delete this block of code, all the unreachable
                # objects will be garbage collected anyway. So, as a test,
                # there is no equivalent of this block of code for
                # history-free storage. If something goes wrong, then we'll
                # know there's some other edge condition we have to account
                # for.
                stmt = "SELECT 1 FROM transaction WHERE tid = %(tid)s"
                cursor.execute(
                    intern(stmt % self.runner.script_vars),
                    {'tid': prev_polled_tid})
                rows = cursor.fetchall()
                if not rows:
                    # Transaction not found; perhaps it has been packed.
                    # The connection cache should be cleared.
                    return None, new_polled_tid

            # Get the list of changed OIDs and return it.
            if self.keep_history:
                stmt = """
                SELECT zoid, tid
                FROM current_object
                WHERE tid > %(tid)s
                """
            else:
                stmt = """
                SELECT zoid, tid
                FROM object_state
                WHERE tid > %(tid)s
                """
            params = {'tid': prev_polled_tid}
            if ignore_tid is not None:
                stmt += " AND tid != %(self_tid)s"
                params['self_tid'] = ignore_tid
            stmt = intern(stmt % self.runner.script_vars)

            cursor.execute(stmt, params)
            changes = cursor.fetchall()

            return changes, new_polled_tid

        else:
            # The database connection is stale. This can happen after
            # reading an asynchronous slave that is not fully up to date.
            # (It may also suggest that transaction IDs are not being created
            # in order, which would be a serious bug leading to consistency
            # violations.)
            if self.revert_when_stale:
                # This client prefers to revert to the old state.
                log.warning(
                    "Reverting to stale transaction ID %d and clearing cache. "
                    "(prev_polled_tid=%d)",
                    new_polled_tid, prev_polled_tid)
                # We have to invalidate the whole cPickleCache, otherwise
                # the cache would be inconsistent with the reverted state.
                return None, new_polled_tid
            else:
                # This client never wants to revert to stale data, so
                # raise ReadConflictError to trigger a retry.
                # We're probably just waiting for async replication
                # to catch up, so retrying could do the trick.
                raise ReadConflictError(
                    "The database connection is stale: new_polled_tid=%d, "
                    "prev_polled_tid=%d." % (new_polled_tid, prev_polled_tid))
示例#7
0
文件: app.py 项目: vpelletier/neoppod
 def _handleConflicts(self, txn_context, tryToResolveConflict):
     result = []
     append = result.append
     # Check for conflicts
     data_dict = txn_context['data_dict']
     object_base_serial_dict = txn_context['object_base_serial_dict']
     object_serial_dict = txn_context['object_serial_dict']
     conflict_serial_dict = txn_context['conflict_serial_dict'].copy()
     txn_context['conflict_serial_dict'].clear()
     resolved_conflict_serial_dict = txn_context[
         'resolved_conflict_serial_dict']
     for oid, conflict_serial_set in conflict_serial_dict.iteritems():
         conflict_serial = max(conflict_serial_set)
         serial = object_serial_dict[oid]
         if ZERO_TID in conflict_serial_set:
             if 1:
                 # XXX: disable deadlock avoidance code until it is fixed
                 logging.info('Deadlock avoidance on %r:%r', dump(oid),
                              dump(serial))
                 # 'data' parameter of ConflictError is only used to report the
                 # class of the object. It doesn't matter if 'data' is None
                 # because the transaction is too big.
                 try:
                     data = data_dict[oid]
                 except KeyError:
                     data = txn_context['cache_dict'][oid]
             else:
                 # Storage refused us from taking object lock, to avoid a
                 # possible deadlock. TID is actually used for some kind of
                 # "locking priority": when a higher value has the lock,
                 # this means we stored objects "too late", and we would
                 # otherwise cause a deadlock.
                 # To recover, we must ask storages to release locks we
                 # hold (to let possibly-competing transactions acquire
                 # them), and requeue our already-sent store requests.
                 # XXX: currently, brute-force is implemented: we send
                 # object data again.
                 # WARNING: not maintained code
                 logging.info('Deadlock avoidance triggered on %r:%r',
                              dump(oid), dump(serial))
                 for store_oid, store_data in data_dict.iteritems():
                     store_serial = object_serial_dict[store_oid]
                     if store_data is CHECKED_SERIAL:
                         self._checkCurrentSerialInTransaction(
                             txn_context, store_oid, store_serial)
                     else:
                         if store_data is None:
                             # Some undo
                             logging.warning(
                                 'Deadlock avoidance cannot reliably'
                                 ' work with undo, this must be implemented.'
                             )
                             conflict_serial = ZERO_TID
                             break
                         self._store(txn_context,
                                     store_oid,
                                     store_serial,
                                     store_data,
                                     unlock=True)
                 else:
                     continue
         else:
             data = data_dict.pop(oid)
             if data is CHECKED_SERIAL:
                 raise ReadConflictError(oid=oid,
                                         serials=(conflict_serial, serial))
             # TODO: data can be None if a conflict happens during undo
             if data:
                 txn_context['data_size'] -= len(data)
             resolved_serial_set = resolved_conflict_serial_dict.setdefault(
                 oid, set())
             if resolved_serial_set and conflict_serial <= max(
                     resolved_serial_set):
                 # A later serial has already been resolved, skip.
                 resolved_serial_set.update(conflict_serial_set)
                 continue
             try:
                 new_data = tryToResolveConflict(oid, conflict_serial,
                                                 serial, data)
             except ConflictError:
                 logging.info(
                     'Conflict resolution failed for '
                     '%r:%r with %r', dump(oid), dump(serial),
                     dump(conflict_serial))
             else:
                 logging.info(
                     'Conflict resolution succeeded for '
                     '%r:%r with %r', dump(oid), dump(serial),
                     dump(conflict_serial))
                 # Mark this conflict as resolved
                 resolved_serial_set.update(conflict_serial_set)
                 # Base serial changes too, as we resolved a conflict
                 object_base_serial_dict[oid] = conflict_serial
                 # Try to store again
                 self._store(txn_context, oid, conflict_serial, new_data)
                 append(oid)
                 continue
         # With recent ZODB, get_pickle_metadata (from ZODB.utils) does
         # not support empty values, so do not pass 'data' in this case.
         raise ConflictError(oid=oid,
                             serials=(conflict_serial, serial),
                             data=data or None)
     return result
示例#8
0
    def setstate(self, obj):
        oid = obj._p_oid

        self.before_load()
        try:
            p, serial = self._storage.load(oid, self._version)
            self._load_count = self._load_count + 1

            # XXX this is quite conservative!
            # We need, however, to avoid reading data from a transaction
            # that committed after the current "session" started, as
            # that might lead to mixing of cached data from earlier
            # transactions and new inconsistent data.
            #
            # Note that we (carefully) wait until after we call the
            # storage to make sure that we don't miss an invaildation
            # notifications between the time we check and the time we
            # read.
            #invalid = self._invalidated.get
            invalid = self._invalidated.__contains__
            if invalid(oid) or invalid(None):
                if not hasattr(obj.__class__, '_p_independent'):
                    transaction.get().register(self)
                    raise ReadConflictError(object=obj)
                invalid = 1
            else:
                invalid = 0

            file = StringIO(p)
            unpickler = Unpickler(file)
            # SDH: external references are reassembled elsewhere.
            # unpickler.persistent_load=self._persistent_load
            classification = unpickler.load()
            state = unpickler.load()

            # SDH: Let the object mapper do the state setting.
            # if hasattr(object, '__setstate__'):
            #     object.__setstate__(state)
            # else:
            #     d=object.__dict__
            #     for k,v in state.items(): d[k]=v
            osio = self._get_osio()
            event = osio.deserialize(oid, obj, classification, state)

            if event.upos:
                self._handle_unmanaged(obj, event.upos)

            self._set_serial(obj, serial)

            if invalid:
                if obj._p_independent():
                    try:
                        self._invalidated.remove(oid)
                    except KeyError:
                        pass
                else:
                    transaction.get().register(self)
                    raise ConflictError(object=obj)

        except ConflictError:
            raise
        except:
            LOG('ZODB',
                ERROR,
                "Couldn't load state for %s" % ` oid `,
                error=sys.exc_info())
            raise
示例#9
0
文件: app.py 项目: sshyran/neoppod
 def _handleConflicts(self, txn_context):
     data_dict = txn_context.data_dict
     pop_conflict = txn_context.conflict_dict.popitem
     resolved_dict = txn_context.resolved_dict
     tryToResolveConflict = txn_context.Storage.tryToResolveConflict
     while 1:
         # We iterate over conflict_dict, and clear it,
         # because new items may be added by calls to _store.
         # This is also done atomically, to avoid race conditions
         # with PrimaryNotificationsHandler.notifyDeadlock
         try:
             oid, serial = pop_conflict()
         except KeyError:
             return
         try:
             data, old_serial, _ = data_dict.pop(oid)
         except KeyError:
             assert oid is None, (oid, serial)
             # Storage refused us from taking object lock, to avoid a
             # possible deadlock. TID is actually used for some kind of
             # "locking priority": when a higher value has the lock,
             # this means we stored objects "too late", and we would
             # otherwise cause a deadlock.
             # To recover, we must ask storages to release locks we
             # hold (to let possibly-competing transactions acquire
             # them), and requeue our already-sent store requests.
             ttid = txn_context.ttid
             logging.info('Deadlock avoidance triggered for TXN %s'
               ' with new locking TID %s', dump(ttid), dump(serial))
             txn_context.locking_tid = serial
             packet = Packets.AskRebaseTransaction(ttid, serial)
             for uuid in txn_context.conn_dict:
                 self._askStorageForWrite(txn_context, uuid, packet)
         else:
             if data is CHECKED_SERIAL:
                 raise ReadConflictError(oid=oid,
                     serials=(serial, old_serial))
             # TODO: data can be None if a conflict happens during undo
             if data:
                 txn_context.data_size -= len(data)
             if self.last_tid < serial:
                 self.sync() # possible late invalidation (very rare)
             try:
                 data = tryToResolveConflict(oid, serial, old_serial, data)
             except ConflictError:
                 logging.info(
                     'Conflict resolution failed for %s@%s with %s',
                     dump(oid), dump(old_serial), dump(serial))
                 # With recent ZODB, get_pickle_metadata (from ZODB.utils)
                 # does not support empty values, so do not pass 'data'
                 # in this case.
                 raise ConflictError(oid=oid, serials=(serial, old_serial),
                                     data=data or None)
             else:
                 logging.info(
                     'Conflict resolution succeeded for %s@%s with %s',
                     dump(oid), dump(old_serial), dump(serial))
                 # Mark this conflict as resolved
                 resolved_dict[oid] = serial
                 # Try to store again
                 self._store(txn_context, oid, serial, data)
示例#10
0
 def _load_before_or_conflict(self, obj):
     """Load non-current state for obj or raise ReadConflictError."""
     if not ((not self._version) and self._setstate_noncurrent(obj)):
         self._register(obj)
         self._conflicts[obj._p_oid] = True
         raise ReadConflictError(object=obj)