[Zodb-checkins] SVN: ZODB/trunk/ Merge r25929 from 3.3 branch.

Tim Peters tim.one at comcast.net
Mon Jun 21 16:50:16 EDT 2004


Log message for revision 25930:
Merge r25929 from 3.3 branch.

Collector 1327:  FileStorage init confused by time travel

If the system clock moved back in time more than 30 days between
a FileStorage close and (re)open, new txn ids could be smaller
than txn ids already in the database.  Plugged that hole.



-=-
Modified: ZODB/trunk/NEWS.txt
===================================================================
--- ZODB/trunk/NEWS.txt	2004-06-21 20:43:04 UTC (rev 25929)
+++ ZODB/trunk/NEWS.txt	2004-06-21 20:48:20 UTC (rev 25930)
@@ -1,3 +1,22 @@
+What's new in ZODB3 3.3
+=======================
+Release date: DD-MMM-2004
+
+Storages
+--------
+
+Collector #1327 FileStorage init confused by time travel.
+If the system clock "went backwards" a long time between the times a
+FileStorage was closed and reopened, new transaction ids could be
+smaller than transaction ids already in the storage, violating a
+key invariant.  Now transaction ids are guaranteed to be increasing
+even when this happens.  If time appears to have run backwards at all
+when a FileStorage is opened, a new message saying so is logged at
+warning level; if time appears to have run backwards at least 30
+minutes, the message is logged at critical level (and you should
+investigate to find and repair the true cause).
+
+
 What's new in ZODB3 3.3 beta 1
 ==============================
 Release date: 07-Jun-2004

Modified: ZODB/trunk/src/ZODB/FileStorage/FileStorage.py
===================================================================
--- ZODB/trunk/src/ZODB/FileStorage/FileStorage.py	2004-06-21 20:43:04 UTC (rev 25929)
+++ ZODB/trunk/src/ZODB/FileStorage/FileStorage.py	2004-06-21 20:48:20 UTC (rev 25930)
@@ -191,10 +191,12 @@
         t = time.time()
         t = TimeStamp(*time.gmtime(t)[:5] + (t % 60,))
         if tid > t:
-            logger.warning("%s Database records in the future", file_name);
-            if tid.timeTime() - t.timeTime() > 86400*30:
-                # a month in the future? This is bogus, use current time
-                self._ts = t
+            seconds = tid.timeTime() - t.timeTime()
+            complainer = logger.warning
+            if seconds > 30 * 60:   # 30 minutes -- way screwed up
+                complainer = logger.critical
+            complainer("%s Database records %d seconds in the future",
+                       file_name, seconds)
 
         self._quota = quota
 

Copied: ZODB/trunk/src/ZODB/tests/loggingsupport.py (from rev 25929, ZODB/branches/3.3/src/ZODB/tests/loggingsupport.py)


Property changes on: ZODB/trunk/src/ZODB/tests/loggingsupport.py
___________________________________________________________________
Name: cvs2svn:cvs-rev
   + 1.1
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: ZODB/trunk/src/ZODB/tests/testFileStorage.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testFileStorage.py	2004-06-21 20:43:04 UTC (rev 25929)
+++ ZODB/trunk/src/ZODB/tests/testFileStorage.py	2004-06-21 20:48:20 UTC (rev 25930)
@@ -205,12 +205,109 @@
             self._storage.tpc_vote(t)
             self._storage.tpc_finish(t)
 
+# Raise an exception if the tids in FileStorage fs aren't
+# strictly increasing.
+def checkIncreasingTids(fs):
+    lasttid = '\0' * 8
+    for txn in fs.iterator():
+        if lasttid >= txn.tid:
+            raise ValueError("tids out of order %r >= %r" % (lasttid, tid))
+        lasttid = txn.tid
 
+# Return a TimeStamp object 'minutes' minutes in the future.
+def timestamp(minutes):
+    import time
+    from persistent.TimeStamp import TimeStamp
+
+    t = time.time() + 60 * minutes
+    return TimeStamp(*time.gmtime(t)[:5] + (t % 60,))
+
+def testTimeTravelOnOpen():
+    """
+    >>> from ZODB.FileStorage import FileStorage
+    >>> from ZODB.DB import DB
+    >>> import transaction
+    >>> from ZODB.tests.loggingsupport import InstalledHandler
+
+    Arrange to capture log messages -- they're an important part of
+    this test!
+
+    >>> handler = InstalledHandler('ZODB.FileStorage')
+
+    Create a new file storage.
+
+    >>> st = FileStorage('temp.fs', create=True)
+    >>> db = DB(st)
+    >>> db.close()
+
+    First check the normal case:  transactions are recorded with
+    increasing tids, and time doesn't run backwards.
+
+    >>> st = FileStorage('temp.fs')
+    >>> db = DB(st)
+    >>> conn = db.open()
+    >>> conn.root()['xyz'] = 1
+    >>> transaction.get().commit()
+    >>> checkIncreasingTids(st)
+    >>> db.close()
+    >>> st.cleanup() # remove .fs, .index, etc files
+    >>> handler.records   # i.e., no log messages
+    []
+
+    Now force the database to have transaction records with tids from
+    the future.
+
+    >>> st = FileStorage('temp.fs', create=True)
+    >>> st._ts = timestamp(15)  # 15 minutes in the future
+    >>> db = DB(st)
+    >>> db.close()
+
+    >>> st = FileStorage('temp.fs') # this should log a warning
+    >>> db = DB(st)
+    >>> conn = db.open()
+    >>> conn.root()['xyz'] = 1
+    >>> transaction.get().commit()
+    >>> checkIncreasingTids(st)
+    >>> db.close()
+    >>> st.cleanup()
+
+    >>> [record.levelname for record in handler.records]
+    ['WARNING']
+    >>> handler.clear()
+
+    And one more time, with transaction records far in the future.
+    We expect to log a critical error then, as a time so far in the
+    future probably indicates a real problem with the system.  Shorter
+    spans may be due to clock drift.
+
+    >>> st = FileStorage('temp.fs', create=True)
+    >>> st._ts = timestamp(60)  # an hour in the future
+    >>> db = DB(st)
+    >>> db.close()
+
+    >>> st = FileStorage('temp.fs') # this should log a critical error
+    >>> db = DB(st)
+    >>> conn = db.open()
+    >>> conn.root()['xyz'] = 1
+    >>> transaction.get().commit()
+    >>> checkIncreasingTids(st)
+    >>> db.close()
+    >>> st.cleanup()
+
+    >>> [record.levelname for record in handler.records]
+    ['CRITICAL']
+    >>> handler.clear()
+    >>> handler.uninstall()
+    """
+
 def test_suite():
+    import doctest
+
     suite = unittest.TestSuite()
     for klass in [FileStorageTests, Corruption.FileStorageCorruptTests,
                   FileStorageRecoveryTest, SlowFileStorageTest]:
         suite.addTest(unittest.makeSuite(klass, "check"))
+    suite.addTest(doctest.DocTestSuite())
     return suite
 
 if __name__=='__main__':




More information about the Zodb-checkins mailing list