def test__p_mtime_w_serial(self):
     from persistent.timestamp import TimeStamp
     WHEN_TUPLE = (2011, 2, 15, 13, 33, 27.5)
     ts = TimeStamp(*WHEN_TUPLE)
     inst, jar, OID = self._makeOneWithJar()
     inst._p_serial = ts.raw()
     self.assertEqual(inst._p_mtime, ts.timeTime())
 def test__p_mtime_w_serial(self):
     from persistent.timestamp import TimeStamp
     WHEN_TUPLE = (2011, 2, 15, 13, 33, 27.5)
     ts = TimeStamp(*WHEN_TUPLE)
     inst, jar, OID = self._makeOneWithJar()
     inst._p_serial = ts.raw()
     self.assertEqual(inst._p_mtime, ts.timeTime())
Example #3
0
 def _get_mtime(self):
     # The C implementation automatically unghostifies the object
     # when _p_mtime is accessed.
     self._p_activate()
     self._p_accessed()
     serial = _OGA(self, '_Persistent__serial')
     if serial is not None:
         ts = TimeStamp(serial)
         return ts.timeTime()
Example #4
0
 def _get_mtime(self):
     # The C implementation automatically unghostifies the object
     # when _p_mtime is accessed.
     self._p_activate()
     self._p_accessed()
     serial = _OGA(self, '_Persistent__serial')
     if serial is not None:
         ts = TimeStamp(serial)
         return ts.timeTime()
Example #5
0
    def get_cache_modification_time_for_stream(self):
        max_tid = 0
        for key in self.local_client:
            parts = key.split(':')
            if len(parts) != 4:
                continue
            tid = int(parts[2])
            max_tid = max(tid, max_tid)

        if max_tid:
            tid_str = p64(max_tid)
            ts = TimeStamp(tid_str)
            return ts.timeTime()
Example #6
0
    def get_cache_modification_time_for_stream(self):
        max_tid = 0
        for key in self.local_client:
            parts = key.split(':')
            if len(parts) != 4:
                continue
            tid = int(parts[2])
            max_tid = max(tid, max_tid)

        if max_tid:
            tid_str = p64(max_tid)
            ts = TimeStamp(tid_str)
            return ts.timeTime()
Example #7
0
    def history(self, oid, version=None, size=1, filter=None):
        # pylint:disable=unused-argument,too-many-locals
        cursor = self.load_connection.cursor
        oid_int = bytes8_to_int64(oid)
        try:
            rows = self.adapter.dbiter.iter_object_history(cursor, oid_int)
        except KeyError:
            raise POSKeyError(oid)

        res = []
        for tid_int, username, description, extension, length in rows:
            tid = int64_to_8bytes(tid_int)
            if extension:
                d = loads(extension)
            else:
                d = {}
            d.update({
                "time": TimeStamp(tid).timeTime(),
                "user_name": username or b'',
                "description": description or b'',
                "tid": tid,
                "version": '',
                "size": length,
                "rs_tid_int": tid_int,
                "rs_oid_int": oid_int,
            })
            if filter is None or filter(d):
                res.append(d)
                if size is not None and len(res) >= size:
                    break
        return res
Example #8
0
    def undoLog(self, first=0, last=-20, filter=None):
        if last < 0:
            last = first - last

        # use a private connection to ensure the most current results
        with self.load_connection.isolated_connection() as cursor:
            tx_iter = self.adapter.dbiter.iter_transactions(cursor)
            i = 0
            res = []
            for tx in tx_iter:
                tid = int64_to_8bytes(tx.tid_int)
                # Note that user and desc are schizophrenic. The transaction
                # interface specifies that they are a Python str, *probably*
                # meaning bytes. But code in the wild and the ZODB test suite
                # sets them as native strings, meaning unicode on Py3. OTOH, the
                # test suite checks that this method *returns* them as bytes!
                # This is largely cleaned up with transaction 2.0/ZODB 5, where the storage
                # interface is defined in terms of bytes only.
                d = {
                    'id': base64_encodebytes(tid)[:-1],  # pylint:disable=deprecated-method
                    'time': TimeStamp(tid).timeTime(),
                    'user_name':  tx.username or b'',
                    'description': tx.description or b'',
                }
                if tx.extension:
                    d.update(loads(tx.extension))

                if filter is None or filter(d):
                    if i >= first:
                        res.append(d)
                    i += 1
                    if i >= last:
                        break
            return res
Example #9
0
    def checkPackWithGCOnDestinationAfterRestore(self):
        raises = self.assertRaises
        closing = self._closing
        __traceback_info__ = self._storage, self._dst
        db = closing(DB(self._storage))
        conn = closing(db.open())
        root = conn.root()
        root.obj = obj1 = MinPO(1)
        txn = transaction.get()
        txn.note(u'root -> obj')
        txn.commit()
        root.obj.obj = obj2 = MinPO(2)
        txn = transaction.get()
        txn.note(u'root -> obj -> obj')
        txn.commit()
        del root.obj
        txn = transaction.get()
        txn.note(u'root -X->')
        txn.commit()

        storage_last_tid = conn._storage.lastTransaction()
        self.assertEqual(storage_last_tid, root._p_serial)

        # Now copy the transactions to the destination
        self._dst.copyTransactionsFrom(self._storage)
        self.assertEqual(self._dst.lastTransaction(), storage_last_tid)
        # If the source storage is a history-free storage, all
        # of the transactions are now marked as packed in the
        # destination storage.  To trigger a pack, we have to
        # add another transaction to the destination that is
        # not packed.
        db2 = closing(DB(self._dst))
        tx_manager = transaction.TransactionManager(explicit=True)
        conn2 = closing(db2.open(tx_manager))
        txn = tx_manager.begin()
        root2 = conn2.root()
        root2.extra = 0
        txn.note(u'root.extra = 0')
        txn.commit()

        dest_last_tid = conn2._storage.lastTransaction()
        self.assertGreater(dest_last_tid, storage_last_tid)
        self.assertEqual(dest_last_tid, root2._p_serial)

        # Now pack the destination.
        from ZODB.utils import u64 as bytes8_to_int64
        if IRelStorage.providedBy(self._dst):
            packtime = bytes8_to_int64(storage_last_tid)
        else:
            from persistent.timestamp import TimeStamp
            packtime = TimeStamp(dest_last_tid).timeTime() + 2
        self._dst.pack(packtime, referencesf)
        # And check to see that the root object exists, but not the other
        # objects.
        __traceback_info__ += (packtime,)
        _data, _serial = self._dst.load(root._p_oid, '')
        raises(KeyError, self._dst.load, obj1._p_oid, '')
        raises(KeyError, self._dst.load, obj2._p_oid, '')
Example #10
0
    def lock_database_and_choose_next_tid(self, cursor, username, description,
                                          extension):
        self.locker.hold_commit_lock(cursor, ensure_current=True)

        # Choose a transaction ID.
        #
        # Base the transaction ID on the current time, but ensure that
        # the tid of this transaction is greater than any existing
        # tid.
        last_tid = self.txncontrol.get_tid(cursor)
        now = time.time()
        stamp = timestamp_at_unixtime(now)
        stamp = stamp.laterThan(TimeStamp(int64_to_8bytes(last_tid)))
        tid = stamp.raw()

        tid_int = bytes8_to_int64(tid)
        self.txncontrol.add_transaction(cursor, tid_int, username, description,
                                        extension)
        logger.log(TRACE, "Picked next tid locally: %s", tid_int)
        return tid_int
Example #11
0
    def undoLog(self, first=0, last=-20, filter=None):
        # pylint:disable=too-many-locals
        if last < 0:
            last = first - last

        # use a private connection to ensure the most current results
        adapter = self.adapter
        conn, cursor = adapter.connmanager.open()
        try:
            rows = adapter.dbiter.iter_transactions(cursor)
            i = 0
            res = []
            for tid_int, user, desc, ext in rows:
                tid = int64_to_8bytes(tid_int)
                # Note that user and desc are schizophrenic. The transaction
                # interface specifies that they are a Python str, *probably*
                # meaning bytes. But code in the wild and the ZODB test suite
                # sets them as native strings, meaning unicode on Py3. OTOH, the
                # test suite checks that this method *returns* them as bytes!
                # This is largely cleaned up with transaction 2.0/ZODB 5, where the storage
                # interface is defined in terms of bytes only.
                d = {
                    'id': base64_encodebytes(tid)[:-1],  # pylint:disable=deprecated-method
                    'time': TimeStamp(tid).timeTime(),
                    'user_name': user or b'',
                    'description': desc or b'',
                }
                if ext:
                    d.update(loads(ext))

                if filter is None or filter(d):
                    if i >= first:
                        res.append(d)
                    i += 1
                    if i >= last:
                        break
            return res

        finally:
            adapter.connmanager.close(conn, cursor)
Example #12
0
    def test_current_time(self, now=None):
        from persistent.timestamp import TimeStamp
        from relstorage._util import int64_to_8bytes
        if now is None:
            now = time.time()
        storage = self._storage
        ts_now = timestamp_at_unixtime(now)

        expected_tid_int = bytes8_to_int64(ts_now.raw())
        __traceback_info__ = now, now % 60.0, time.gmtime(
            now), ts_now, expected_tid_int

        cursor = storage._load_connection.cursor

        cursor.execute('CALL make_tid_for_epoch(%s, @tid)', (now, ))
        cursor.execute('SELECT @tid')
        tid, = cursor.fetchall()[0]

        tid_as_timetime = TimeStamp(int64_to_8bytes(tid)).timeTime()
        __traceback_info__ += (tid_as_timetime - ts_now.timeTime(), )

        self.assertEqual(tid, expected_tid_int)
Example #13
0
def timestamp_at_unixtime(now):
    """
    Return a :class:`persistent.timestamp.TimeStamp` for the moment
    given by *now* (a float giving seconds since the epoch).
    """

    # ``now`` is a float (double precision.). We extract the seconds
    # value using ``now % 60.0``. But performing a modulus operation
    # on it tends to invent precision that we do not really have.
    #
    # For example, given the timestamp ``1564178539.353595``, the
    # correct value for seconds is obviously ``19.353595``. But Python
    # tends to answer ``19.35359501838684``. When the TimeStamp
    # constructor takes that double precision value and applies *more*
    # arithmetic to it to set the bias, the results get worse: we get
    # ``1385384294.4`` when we should get ``1385384293.0838187``.
    #
    # If we're comparing with a system that doesn't use exactly IEEE double
    # arithmetic to calculate the seconds value, this can lead to wrong answers.

    gmtime = time.gmtime(now)
    seconds = now % 60.0
    return TimeStamp(*(gmtime[:5] + (seconds, )))
Example #14
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)
Example #15
0
def readable_tid_repr(tid):
    result = tid_repr(tid)
    if isinstance(tid, bytes) and len(tid) == 8:
        result = "%s %s" % (result, TimeStamp(tid))
    return result
Example #16
0
def newTid(old):
    t = time.time()
    ts = TimeStamp(*time.gmtime(t)[:5]+(t%60,))
    if old is not None:
        ts = ts.laterThan(TimeStamp(old))
    return ts.raw()
Example #17
0
def doit(srcdb, dstdb, options):
    outfp = options.outfp
    profilep = options.profilep
    verbose = options.verbose
    # some global information
    largest_pickle = 0
    largest_txn_in_size = 0
    largest_txn_in_objects = 0
    total_pickle_size = 0
    total_object_count = 0
    # Ripped from BaseStorage.copyTransactionsFrom()
    ts = None
    ok = True
    prevrevids = {}
    counter = 0
    skipper = 0
    if options.timestamps:
        print("%4s. %26s %6s %8s %5s %5s %5s %5s %5s" % (
            "NUM", "TID AS TIMESTAMP", "OBJS", "BYTES",
            # Does anybody know what these times mean?
            "t4-t0", "t1-t0", "t2-t1", "t3-t2", "t4-t3"))
    else:
        print("%4s. %20s %6s %8s %6s %6s %6s %6s %6s" % (
            "NUM", "TRANSACTION ID", "OBJS", "BYTES",
            # Does anybody know what these times mean?
            "t4-t0", "t1-t0", "t2-t1", "t3-t2", "t4-t3"))
    for txn in srcdb.iterator():
        skipper += 1
        if skipper <= options.skiptxn:
            continue
        counter += 1
        if counter > options.maxtxn >= 0:
            break
        tid = txn.tid
        if ts is None:
            ts = TimeStamp(tid)
        else:
            t = TimeStamp(tid)
            if t <= ts:
                if ok:
                    print((
                        'Time stamps are out of order %s, %s' % (ts, t)), file=sys.stderr)
                    ok = False
                    ts = t.laterThan(ts)
                    tid = ts.raw()
                else:
                    ts = t
                    if not ok:
                        print((
                            'Time stamps are back in order %s' % t), file=sys.stderr)
                        ok = True
        if verbose > 1:
            print(ts)

        prof = None
        if profilep and (counter % 100) == 0:
            prof = profile.Profile()
        objects = 0
        size = 0
        newrevids = RevidAccumulator()
        t0 = time.time()
        dstdb.tpc_begin(txn, tid, txn.status)
        t1 = time.time()
        for r in txn:
            oid = r.oid
            objects += 1
            thissize = len(r.data)
            size += thissize
            if thissize > largest_pickle:
                largest_pickle = thissize
            if verbose > 1:
                if not r.version:
                    vstr = 'norev'
                else:
                    vstr = r.version
                print(utils.U64(oid), vstr, len(r.data))
            oldrevid = prevrevids.get(oid, utils.z64)
            result = dstdb.store(oid, oldrevid, r.data, r.version, txn)
            newrevids.store(oid, result)
        t2 = time.time()
        result = dstdb.tpc_vote(txn)
        t3 = time.time()
        newrevids.tpc_vote(result)
        prevrevids.update(newrevids.get_dict())
        # Profile every 100 transactions
        if prof:
            prof.runcall(dstdb.tpc_finish, txn)
        else:
            dstdb.tpc_finish(txn)
        t4 = time.time()

        # record the results
        if objects > largest_txn_in_objects:
            largest_txn_in_objects = objects
        if size > largest_txn_in_size:
            largest_txn_in_size = size
        if options.timestamps:
            tidstr = str(TimeStamp(tid))
            format = "%4d. %26s %6d %8d %5.3f %5.3f %5.3f %5.3f %5.3f"
        else:
            tidstr = utils.U64(tid)
            format = "%4d. %20s %6d %8d %6.4f %6.4f %6.4f %6.4f %6.4f"
        print(format % (skipper, tidstr, objects, size,
                                  t4-t0, t1-t0, t2-t1, t3-t2, t4-t3), file=outfp)
        total_pickle_size += size
        total_object_count += objects

        if prof:
            prof.create_stats()
            fp = open('profile-%02d.txt' % (counter / 100), 'wb')
            marshal.dump(prof.stats, fp)
            fp.close()
    print("Largest pickle:          %8d" % largest_pickle, file=outfp)
    print("Largest transaction:     %8d" % largest_txn_in_size, file=outfp)
    print("Largest object count:    %8d" % largest_txn_in_objects, file=outfp)
    print("Total pickle size: %14d" % total_pickle_size, file=outfp)
    print("Total object count:      %8d" % total_object_count, file=outfp)
Example #18
0
    def __pre_pack(self, t, referencesf):
        logger.info("pack: beginning pre-pack")

        # In 2019, Unix timestamps look like
        #            1564006806.0
        # While 64-bit integer TIDs for the same timestamp look like
        #    275085010696509852
        #
        # Multiple TIDs can map to a single Unix timestamp.
        # For example, the 9 integers between 275085010624927044 and
        # 275085010624927035 all map to 1564006804.9999998.
        #
        # Therefore, Unix timestamps are ambiguous, especially if we're committing
        # multiple transactions rapidly (within the resolution of the underlying TID
        # clock).
        # This ambiguity mostly matters for unit tests, where we do commit rapidly.
        #
        # To help them out, we accept 64-bit integer TIDs to specify an exact
        # transaction to pack to.

        # We also allow None or a negative number to mean "current committed transaction".
        if t is None:
            t = -1

        if t > 275085010696509852:
            # Must be a TID.

            # Turn it back into a time.time() for later logging
            ts = TimeStamp(int64_to_8bytes(t))
            logger.debug(
                "Treating requested pack time %s as TID meaning %s",
                t, ts
            )
            best_pack_tid_int = t
            t = ts.timeTime()
        elif t < 0 or t >= time.time():
            # Packing for the current time or in the future means to pack
            # to the lastest commit in the database. This matters if not all
            # machine clocks are synchronized.
            best_pack_tid_int = MAX_TID - 1
        else:
            # Find the latest commit before or at the pack time.
            # Note that several TIDs will fit in the resolution of a time.time(),
            # so this is slightly ambiguous.
            requested_pack_ts = TimeStamp(*time.gmtime(t)[:5] + (t % 60,))
            requested_pack_tid = requested_pack_ts.raw()
            requested_pack_tid_int = bytes8_to_int64(requested_pack_tid)

            best_pack_tid_int = requested_pack_tid_int

        tid_int = self.packundo.choose_pack_transaction(best_pack_tid_int)

        if tid_int is None:
            logger.debug("all transactions before %s have already "
                         "been packed", time.ctime(t))
            return

        s = time.ctime(TimeStamp(int64_to_8bytes(tid_int)).timeTime())
        logger.info("Analyzing transactions committed %s or before (TID %d)",
                    s, tid_int)

        # In pre_pack, the adapter fills tables with
        # information about what to pack.  The adapter
        # must not actually pack anything yet.
        def get_references(state):
            """Return an iterable of the set of OIDs the given state refers to."""
            if not state:
                return ()

            return {bytes8_to_int64(oid) for oid in referencesf(state)}

        self.packundo.pre_pack(tid_int, get_references)
        logger.info("pack: pre-pack complete")
        return tid_int
Example #19
0
def main(argv=None):
    # pylint:disable=too-many-branches,too-many-statements
    if argv is None:
        argv = sys.argv
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--dry-run",
        dest="dry_run",
        action="store_true",
        default=False,
        help="Attempt to open both storages, then explain what would be done.")
    parser.add_argument(
        "--clear",
        dest="clear",
        action="store_true",
        default=False,
        help="Clear the contents of the destination storage before copying."
        " Only works if the destination is a RelStorage."
        " WARNING: use this only if you are certain the destination has no useful data."
    )
    parser.add_argument(
        "--incremental",
        dest="incremental",
        action="store_true",
        help="Assume the destination contains a partial copy of the source "
        "and resume copying from the last transaction. WARNING: no "
        "effort is made to verify that the destination holds the same "
        "transaction data before this point! Use at your own risk. ")
    parser.add_argument("config_file", type=argparse.FileType('r'))

    options = parser.parse_args(argv[1:])

    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(name)s] %(levelname)s %(message)s")

    source, destination = open_storages(options)

    def cleanup_and_exit(exit_msg=None):
        source.close()
        destination.close()
        if exit_msg:
            sys.exit(msg)

    log.info("Storages opened successfully.")

    if options.incremental:
        assert hasattr(destination, 'lastTransaction'), (
            "Error: no API is known for determining the last committed "
            "transaction of the destination storage. Aborting "
            "conversion.")

        if not storage_has_data(destination):
            log.warning(
                "Destination empty, start conversion from the beginning.")
        else:
            # This requires that the storage produce a valid (not z64) value before
            # anything is loaded with it.
            last_tid = destination.lastTransaction()
            if isinstance(last_tid, bytes):
                # This *should* be a byte string.
                last_tid = u64(last_tid)

            next_tid = p64(last_tid + 1)
            # Compensate for the RelStorage bug(?) and get a reusable iterator
            # that starts where we want it to. There's no harm in wrapping it for
            # other sources like FileStorage too.
            source = _DefaultStartStorageIteration(source, next_tid)
            log.info("Resuming ZODB copy from %s", readable_tid_repr(next_tid))

    if options.dry_run:
        log.info("Dry run mode: not changing the destination.")
        if storage_has_data(destination):
            log.warning("The destination storage has data.")
        count = 0
        for txn in source.iterator():
            log.info('%s user=%s description=%s', TimeStamp(txn.tid), txn.user,
                     txn.description)
            count += 1
        log.info("Would copy %d transactions.", count)
        cleanup_and_exit()
    else:
        if options.clear:
            log.info("Clearing old data...")
            if hasattr(destination, 'zap_all'):
                destination.zap_all()
            else:
                msg = ("Error: no API is known for clearing this type "
                       "of storage. Use another method.")
                cleanup_and_exit(msg)
            log.info("Done clearing old data.")

        if storage_has_data(destination) and not options.incremental:
            msg = "Error: the destination storage has data.  Try --clear."
            cleanup_and_exit(msg)

        destination.copyTransactionsFrom(source)
        cleanup_and_exit()
Example #20
0
 def _makeC(self, *args, **kwargs):
     from persistent.timestamp import TimeStamp
     return TimeStamp(*args, **kwargs)
 def _get_mtime(self):
     if self.__serial is not None:
         ts = TimeStamp(self.__serial)
         return ts.timeTime()
Example #22
0
 def _get_mtime(self):
     if self.__serial is not None:
         ts = TimeStamp(self.__serial)
         return ts.timeTime()