Bug 777328 - Gather telemetry data for how much cache corruption reduction plan would help. r=michal
authorBrian R. Bondy <netzen@gmail.com>
Mon, 20 Aug 2012 08:08:46 -0400
changeset 102816 f11e42f6678bcb9c0c77c931b437226bd126c6be
parent 102815 0255e9c7f468ab236654240c385556c31a538dd6
child 102817 b48a2499872cb194b54d8b7fc91b8e495396c9a1
push id13666
push userbbondy@mozilla.com
push dateMon, 20 Aug 2012 17:35:29 +0000
treeherdermozilla-inbound@f11e42f6678b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs777328
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 777328 - Gather telemetry data for how much cache corruption reduction plan would help. r=michal
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsDiskCache.h
netwerk/cache/nsDiskCacheMap.cpp
netwerk/cache/nsDiskCacheMap.h
toolkit/components/telemetry/TelemetryHistograms.h
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -2847,16 +2847,22 @@ nsCacheService::ClearPendingRequests(nsC
 
         // XXX we're just dropping these on the floor for now...definitely wrong.
         PR_REMOVE_AND_INIT_LINK(request);
         delete request;
         request = next;
     }
 }
 
+bool
+nsCacheService::IsDoomListEmpty()
+{
+    nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+    return &mDoomedEntries == entry;
+}
 
 void
 nsCacheService::ClearDoomList()
 {
     nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
 
     while (entry != &mDoomedEntries) {
         nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -173,28 +173,30 @@ public:
 
     nsresult         Init();
     void             Shutdown();
 
     static void      AssertOwnsLock()
     { gService->mLock.AssertCurrentThreadOwns(); }
 
     static void      LeavePrivateBrowsing();
+    bool             IsDoomListEmpty();
 
     typedef bool (*DoomCheckFn)(nsCacheEntry* entry);
 
 private:
     friend class nsCacheServiceAutoLock;
     friend class nsOfflineCacheDevice;
     friend class nsProcessRequestEvent;
     friend class nsSetSmartSizeEvent;
     friend class nsBlockOnCacheThreadEvent;
     friend class nsSetDiskSmartSizeCallback;
     friend class nsDoomEvent;
     friend class nsDisableOldMaxSmartSizePrefEvent;
+    friend class nsDiskCacheMap;
 
     /**
      * Internal Methods
      */
 
     static void      Lock(::mozilla::Telemetry::ID mainThreadLockerID);
     static void      Unlock();
 
--- a/netwerk/cache/nsDiskCache.h
+++ b/netwerk/cache/nsDiskCache.h
@@ -44,17 +44,20 @@ public:
       kEntryCountIncorrect = 14,
       kCouldNotGetBlockFileForIndex = 15,
       kCouldNotCreateBlockFile = 16,
       kBlockFileSizeError = 17,
       kBlockFileBitMapWriteError = 18,
       kBlockFileSizeLessThanBitMap = 19,
       kBlockFileBitMapReadError = 20,
       kBlockFileEstimatedSizeError = 21,
-      kFlushHeaderError = 22
+      kFlushHeaderError = 22,
+      kCacheCleanFilePathError = 23,
+      kCacheCleanOpenFileError = 24,
+      kCacheCleanTimerError = 25
     };
 
     // Parameter initval initializes internal state of hash function. Hash values are different
     // for the same text when different initval is used. It can be any random number.
     // 
     // It can be used for generating 64-bit hash value:
     //   (PRUint64(Hash(key, initval1)) << 32) | Hash(key, initval2)
     //
--- a/netwerk/cache/nsDiskCacheMap.cpp
+++ b/netwerk/cache/nsDiskCacheMap.cpp
@@ -2,27 +2,30 @@
 /* vim:set ts=4 sw=4 sts=4 cin et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsDiskCacheMap.h"
 #include "nsDiskCacheBinding.h"
 #include "nsDiskCacheEntry.h"
+#include "nsCacheService.h"
 
 #include "nsCache.h"
 
 #include <string.h>
 #include "nsPrintfCString.h"
 
 #include "nsISerializable.h"
 #include "nsSerializationHelper.h"
 
 #include "mozilla/Telemetry.h"
 
+using namespace mozilla;
+
 /******************************************************************************
  *  nsDiskCacheMap
  *****************************************************************************/
 
 /**
  *  File operations
  */
 
@@ -51,19 +54,24 @@ nsDiskCacheMap::Open(nsIFile *  cacheDir
     if (NS_FAILED(rv)) {
         *corruptInfo = nsDiskCache::kOpenCacheMapError;
         NS_WARNING("Could not open cache map file");
         return NS_ERROR_FILE_CORRUPTED;
     }
 
     bool cacheFilesExist = CacheFilesExist();
     rv = NS_ERROR_FILE_CORRUPTED;  // presume the worst
+    PRUint32 mapSize = PR_Available(mMapFD);    
+
+    if (NS_FAILED(InitCacheClean(cacheDirectory, corruptInfo))) {
+        // corruptInfo is set in the call to InitCacheClean
+        goto error_exit;
+    }
 
     // check size of map file
-    PRUint32 mapSize = PR_Available(mMapFD);    
     if (mapSize == 0) {  // creating a new _CACHE_MAP_
 
         // block files shouldn't exist if we're creating the _CACHE_MAP_
         if (cacheFilesExist) {
             *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
             goto error_exit; 
         }
 
@@ -168,18 +176,17 @@ nsDiskCacheMap::Open(nsIFile *  cacheDir
         *corruptInfo = nsDiskCache::kFlushHeaderError;
         goto error_exit;
     }
     
     {
         // extra scope so the compiler doesn't barf on the above gotos jumping
         // past this declaration down here
         PRUint32 overhead = moz_malloc_size_of(mRecordArray);
-        mozilla::Telemetry::Accumulate(mozilla::Telemetry::HTTP_DISK_CACHE_OVERHEAD,
-                overhead);
+        Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD, overhead);
     }
 
     *corruptInfo = nsDiskCache::kNotCorrupt;
     return NS_OK;
     
 error_exit:
     (void) Close(false);
        
@@ -187,16 +194,22 @@ error_exit:
 }
 
 
 nsresult
 nsDiskCacheMap::Close(bool flush)
 {
     nsresult  rv = NS_OK;
 
+    // Cancel any pending cache validation event, the FlushRecords call below
+    // will validate the cache.
+    if (mCleanCacheTimer) {
+        mCleanCacheTimer->Cancel();
+    }
+
     // If cache map file and its block files are still open, close them
     if (mMapFD) {
         // close block files
         rv = CloseBlockFiles(flush);
         if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
             // write the map records
             rv = FlushRecords(false);   // don't bother swapping buckets back
             if (NS_SUCCEEDED(rv)) {
@@ -205,16 +218,22 @@ nsDiskCacheMap::Close(bool flush)
                 rv = FlushHeader();
             }
         }
         if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
             rv = NS_ERROR_UNEXPECTED;
 
         mMapFD = nullptr;
     }
+
+    if (mCleanFD) {
+        PR_Close(mCleanFD);
+        mCleanFD = nullptr;
+    }
+
     PR_FREEIF(mRecordArray);
     PR_FREEIF(mBuffer);
     mBufferSize = 0;
     return rv;
 }
 
 
 nsresult
@@ -230,16 +249,17 @@ nsDiskCacheMap::Trim()
     if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
     return rv2;
 }
 
 
 nsresult
 nsDiskCacheMap::FlushHeader()
 {
+    RevalidateCache();
     if (!mMapFD)  return NS_ERROR_NOT_AVAILABLE;
     
     // seek to beginning of cache map
     PRInt32 filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
     if (filePos != 0)  return NS_ERROR_UNEXPECTED;
     
     // write the header
     mHeader.Swap();
@@ -342,16 +362,19 @@ nsDiskCacheMap::GrowRecords()
         // clear unused records
         memset(newRecords + count, 0,
                (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
     }
 
     // Set as the new record array
     mRecordArray = newArray;
     mHeader.mRecordCount = newCount;
+
+    InvalidateCache();
+
     return NS_OK;
 }
 
 nsresult
 nsDiskCacheMap::ShrinkRecords()
 {
     if (mHeader.mRecordCount <= kMinRecordCount)
         return NS_OK;
@@ -388,16 +411,19 @@ nsDiskCacheMap::ShrinkRecords()
     nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
             PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
     if (!newArray)
         return NS_ERROR_OUT_OF_MEMORY;
 
     // Set as the new record array
     mRecordArray = newArray;
     mHeader.mRecordCount = newCount;
+
+    InvalidateCache();
+
     return NS_OK;
 }
 
 nsresult
 nsDiskCacheMap::AddRecord( nsDiskCacheRecord *  mapRecord,
                            nsDiskCacheRecord *  oldRecord)
 {
     CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
@@ -416,31 +442,33 @@ nsDiskCacheMap::AddRecord( nsDiskCacheRe
     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
     if (count < GetRecordsPerBucket()) {
         // stick the new record at the end
         records[count] = *mapRecord;
         mHeader.mEntryCount++;
         mHeader.mBucketUsage[bucketIndex]++;           
         if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
             mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+        InvalidateCache();
     } else {
         // Find the record with the highest eviction rank
         nsDiskCacheRecord * mostEvictable = &records[0];
         for (int i = count-1; i > 0; i--) {
             if (records[i].EvictionRank() > mostEvictable->EvictionRank())
                 mostEvictable = &records[i];
         }
         *oldRecord     = *mostEvictable;    // i == GetRecordsPerBucket(), so
                                             // evict the mostEvictable
         *mostEvictable = *mapRecord;        // replace it with the new record
         // check if we need to update mostEvictable entry in header
         if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
             mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
         if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex]) 
             mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+        InvalidateCache();
     }
 
     NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
                  "eviction rank out of sync");
     return NS_OK;
 }
 
 
@@ -461,16 +489,18 @@ nsDiskCacheMap::UpdateRecord( nsDiskCach
             records[i] = *mapRecord;
 
             // update eviction rank in header if necessary
             if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
                 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
             else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
                 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
 
+            InvalidateCache();
+
 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
              "eviction rank out of sync");
             return NS_OK;
         }
     }
     NS_NOTREACHED("record not found");
     return NS_ERROR_UNEXPECTED;
 }
@@ -516,16 +546,18 @@ nsDiskCacheMap::DeleteRecord( nsDiskCach
             mHeader.mEntryCount--;
 
             // update eviction rank
             PRUint32  bucketIndex = GetBucketIndex(mapRecord->HashNumber());
             if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
                 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
             }
 
+            InvalidateCache();
+
             NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
                          GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
             return NS_OK;
         }
     }
     return NS_ERROR_UNEXPECTED;
 }
 
@@ -546,16 +578,17 @@ nsDiskCacheMap::VisitEachRecord(PRUint32
         rv = visitor->VisitRecord(&records[i]);
         if (rv == kStopVisitingRecords) 
             break;    // Stop visiting records
         
         if (rv == kDeleteRecordAndContinue) {
             --count;
             records[i] = records[count];
             records[count].SetHashNumber(0);
+            InvalidateCache();
         }
     }
 
     if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
         mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
         mHeader.mBucketUsage[bucketIndex] = count;
         // recalc eviction rank
         mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
@@ -1169,8 +1202,191 @@ nsDiskCacheMap::NotifyCapacityChange(PRU
   //              map in memory.
   const PRInt32 RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
   PRInt32 maxRecordCount = NS_MIN(PRInt32(capacity), RECORD_COUNT_LIMIT);
   if (mMaxRecordCount < maxRecordCount) {
     // We can only grow
     mMaxRecordCount = maxRecordCount;
   }
 }
+
+nsresult
+nsDiskCacheMap::InitCacheClean(nsIFile *  cacheDirectory,
+                               nsDiskCache::CorruptCacheInfo *  corruptInfo)
+{
+    // The _CACHE_CLEAN_ file will be used in the future to determine
+    // if the cache is clean or not. 
+    bool cacheCleanFileExists = false;
+    nsCOMPtr<nsIFile> cacheCleanFile;
+    nsresult rv = cacheDirectory->Clone(getter_AddRefs(cacheCleanFile));
+    if (NS_SUCCEEDED(rv)) {
+        rv = cacheCleanFile->AppendNative(
+                 NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
+        if (NS_SUCCEEDED(rv)) {
+            // Check if the file already exists, if it does, we will later read the
+            // value and report it to telemetry.
+            cacheCleanFile->Exists(&cacheCleanFileExists);
+        }
+    }
+    if (NS_FAILED(rv)) {
+        NS_WARNING("Could not build cache clean file path");
+        *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
+        return rv;
+    }
+
+    // Make sure the _CACHE_CLEAN_ file exists
+    rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
+                                          00600, &mCleanFD);
+    if (NS_FAILED(rv)) {
+        NS_WARNING("Could not open cache clean file");
+        *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
+        return rv;
+    }
+
+    if (cacheCleanFileExists) {
+        char clean = '0';
+        PRInt32 bytesRead = PR_Read(mCleanFD, &clean, 1);
+        if (bytesRead != 1) {
+            NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
+        } else {
+            Telemetry::Accumulate(Telemetry::DISK_CACHE_REDUCTION_TRIAL,
+                                  clean == '1' ? 1 : 0);
+        }
+    }
+
+    // Create a timer that will be used to validate the cache
+    // as long as an activity threshold was met
+    mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+    if (NS_SUCCEEDED(rv)) {
+        mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread);
+        rv = ResetCacheTimer();
+    }
+
+    if (NS_FAILED(rv)) {
+        NS_WARNING("Could not create cache clean timer");
+        mCleanCacheTimer = nullptr;
+        *corruptInfo = nsDiskCache::kCacheCleanTimerError;
+        return rv;
+    }
+
+    return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::WriteCacheClean(bool clean)
+{
+    nsCacheService::AssertOwnsLock();
+    CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
+    // I'm using a simple '1' or '0' to denote cache clean
+    // since it can be edited easily by any text editor for testing.
+    char data = clean? '1' : '0';
+    PRInt32 filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
+    if (filePos != 0) {
+        NS_WARNING("Could not seek in cache map file!");
+        return NS_ERROR_FAILURE;
+    }
+    PRInt32 bytesWritten = PR_Write(mCleanFD, &data, 1);
+    if (bytesWritten != 1) {
+        NS_WARNING("Could not write cache map file!");
+        return NS_ERROR_FAILURE;
+    }
+    PRStatus err = PR_Sync(mCleanFD);
+    if (err != PR_SUCCESS) {
+        NS_WARNING("Could not flush mCleanFD!");
+    }
+
+    return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::InvalidateCache()
+{
+    nsCacheService::AssertOwnsLock();
+    CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
+    nsresult rv;
+  
+    if (!mIsDirtyCacheFlushed) {
+        rv = WriteCacheClean(false);
+        NS_ENSURE_SUCCESS(rv, rv);
+        mIsDirtyCacheFlushed = true;
+    }
+
+    rv = ResetCacheTimer();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::ResetCacheTimer(PRInt32 timeout)
+{
+    mCleanCacheTimer->Cancel();
+    nsresult rv =
+      mCleanCacheTimer->InitWithFuncCallback(RevalidateTimerCallback,
+                                             this, timeout,
+                                             nsITimer::TYPE_ONE_SHOT);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mLastInvalidateTime = PR_IntervalNow();
+
+    return rv;
+}
+
+void
+nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
+{
+    nsDiskCacheMap *diskCacheMap = reinterpret_cast<nsDiskCacheMap *>(arg);
+    nsresult rv;
+
+    // Intentional braces to scope mutex to only what is needed
+    {
+        nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEMAP_REVALIDATION));
+        // If we have less than kLastInvalidateTime since the last timer was
+        // issued then another thread called InvalidateCache.  This won't catch
+        // all cases where we wanted to cancel the timer, but under the lock it
+        // is always OK to revalidate as long as IsCacheInSafeState() returns
+        // true.  We just want to avoid revalidating when we can to reduce IO
+        // and this check will do that.
+        PRUint32 delta =
+            PR_IntervalToMilliseconds(PR_IntervalNow() -
+                                      diskCacheMap->mLastInvalidateTime) +
+            kRevalidateCacheTimeoutTolerance;
+        if (delta < kRevalidateCacheTimeout) {
+            diskCacheMap->ResetCacheTimer();
+            return;
+        }
+        rv = diskCacheMap->RevalidateCache();
+    }
+
+    if (NS_FAILED(rv)) {
+        diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
+    }
+}
+
+bool
+nsDiskCacheMap::IsCacheInSafeState()
+{
+    return nsCacheService::GlobalInstance()->IsDoomListEmpty();
+}
+
+nsresult
+nsDiskCacheMap::RevalidateCache()
+{
+    CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
+    nsresult rv;
+
+    if (!IsCacheInSafeState()) {
+        CACHE_LOG_DEBUG(("CACHE: Revalidation not performed because "
+                         "cache not in a safe state\n"));
+        return NS_ERROR_FAILURE;
+    }
+
+    // We want this after the lock to prove that flushing a file isn't that expensive
+    Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_REVALIDATION> totalTimer;
+
+    // If telemetry data shows it is worth it, we'll be flushing headers and
+    // records before flushing the clean cache file.
+  
+    // Write out the _CACHE_CLEAN_ file with '1'
+    rv = WriteCacheClean(true);
+    mIsDirtyCacheFlushed = false;
+
+    return NS_OK;
+}
--- a/netwerk/cache/nsDiskCacheMap.h
+++ b/netwerk/cache/nsDiskCacheMap.h
@@ -9,16 +9,17 @@
 
 #include <limits.h>
 
 #include "prtypes.h"
 #include "prnetdb.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIFile.h"
+#include "nsITimer.h"
 
 #include "nsDiskCache.h"
 #include "nsDiskCacheBlockFile.h"
  
  
 class nsDiskCacheBinding;
 struct nsDiskCacheEntry;
 
@@ -73,16 +74,22 @@ struct nsDiskCacheEntry;
 // inaccurate (smaller) than the actual cache size. The alternative is to stat
 // the files to find the real size, which was decided against for performance
 // reasons. See bug #651100 comment #21.
 #define kMaxDataSizeK      0xFFFF
 
 // preallocate up to 1MB of separate cache file
 #define kPreallocateLimit  1 * 1024 * 1024
 
+// The minimum amount of milliseconds to wait before re-attempting to
+// revalidate the cache.
+#define kRevalidateCacheTimeout 5000
+#define kRevalidateCacheTimeoutTolerance 10
+#define kRevalidateCacheErrorTimeout 1000
+
 class nsDiskCacheRecord {
 
 private:
     PRUint32    mHashNumber;
     PRUint32    mEvictionRank;
     PRUint32    mDataLocation;
     PRUint32    mMetaLocation;
  
@@ -373,23 +380,27 @@ struct nsDiskCacheHeader {
  *****************************************************************************/
 
 class nsDiskCacheMap {
 public:
 
      nsDiskCacheMap() : 
         mCacheDirectory(nullptr),
         mMapFD(nullptr),
+        mCleanFD(nullptr),
         mRecordArray(nullptr),
         mBufferSize(0),
         mBuffer(nullptr),
-        mMaxRecordCount(16384) // this default value won't matter
+        mMaxRecordCount(16384), // this default value won't matter
+        mIsDirtyCacheFlushed(false),
+        mLastInvalidateTime(0)
     { }
 
-    ~nsDiskCacheMap() {
+    ~nsDiskCacheMap()
+    {
         (void) Close(true);
     }
 
 /**
  *  File Operations
  *
  *  Open
  *
@@ -521,23 +532,44 @@ private:
 
     nsresult EnsureBuffer(PRUint32 bufSize);
 
     // The returned structure will point to the buffer owned by nsDiskCacheMap, 
     // so it must not be deleted by the caller.
     nsDiskCacheEntry *  CreateDiskCacheEntry(nsDiskCacheBinding *  binding,
                                              PRUint32 * size);
 
-/**
+    // Initializes the _CACHE_CLEAN_ related functionality
+    nsresult InitCacheClean(nsIFile *  cacheDirectory,
+                            nsDiskCache::CorruptCacheInfo *  corruptInfo);
+    // Writes out a value of '0' or '1' in the _CACHE_CLEAN_ file
+    nsresult WriteCacheClean(bool clean);
+    // Resets the timout for revalidating the cache
+    nsresult ResetCacheTimer(PRInt32 timeout = kRevalidateCacheTimeout);
+    // Invalidates the cache, calls WriteCacheClean and ResetCacheTimer
+    nsresult InvalidateCache();
+    // Determines if the cache is in a safe state
+    bool IsCacheInSafeState();
+    // Revalidates the cache by writting out the header, records, and finally
+    // by calling WriteCacheClean(true).
+    nsresult RevalidateCache();
+    // Timer which revalidates the cache
+    static void RevalidateTimerCallback(nsITimer *aTimer, void *arg);
+
+/** 
  *  data members
  */
 private:
+    nsCOMPtr<nsITimer>      mCleanCacheTimer;
     nsCOMPtr<nsIFile>       mCacheDirectory;
     PRFileDesc *            mMapFD;
+    PRFileDesc *            mCleanFD;
     nsDiskCacheRecord *     mRecordArray;
     nsDiskCacheBlockFile    mBlockFile[kNumBlockFiles];
     PRUint32                mBufferSize;
     char *                  mBuffer;
     nsDiskCacheHeader       mHeader;
     PRInt32                 mMaxRecordCount;
+    bool                    mIsDirtyCacheFlushed;
+    PRIntervalTime          mLastInvalidateTime;
 };
 
 #endif // _nsDiskCacheMap_h_
--- a/toolkit/components/telemetry/TelemetryHistograms.h
+++ b/toolkit/components/telemetry/TelemetryHistograms.h
@@ -196,16 +196,17 @@ HISTOGRAM(SPDY_SETTINGS_MAX_STREAMS, 1, 
 HISTOGRAM(SPDY_SETTINGS_CWND, 1, 500, 50, EXPONENTIAL,  "SPDY: Settings CWND (packets)")
 HISTOGRAM(SPDY_SETTINGS_RETRANS, 1, 100, 50, EXPONENTIAL,  "SPDY: Retransmission Rate")
 HISTOGRAM(SPDY_SETTINGS_IW, 1, 1000, 50, EXPONENTIAL,  "SPDY: Settings IW (rounded to KB)")
 
 #undef _HTTP_HIST
 #undef HTTP_HISTOGRAMS
 
 HISTOGRAM(DISK_CACHE_CORRUPT_DETAILS, 1, 50, 51, LINEAR, "Why the HTTP disk cache was corrupted at startup")
+HISTOGRAM_BOOLEAN(DISK_CACHE_REDUCTION_TRIAL, "Stores 1 if the cache would be corrupted with the disk cache corruption plan of Bug 105843")
 HISTOGRAM_ENUMERATED_VALUES(HTTP_CACHE_DISPOSITION_2,         5, "HTTP Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM_ENUMERATED_VALUES(HTTP_DISK_CACHE_DISPOSITION_2,    5, "HTTP Disk Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM_ENUMERATED_VALUES(HTTP_MEMORY_CACHE_DISPOSITION_2,  5, "HTTP Memory Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM_ENUMERATED_VALUES(HTTP_OFFLINE_CACHE_DISPOSITION_2, 5, "HTTP Offline Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(CACHE_DEVICE_SEARCH, 1, 100, 100, LINEAR, "Time to search cache (ms)")
 HISTOGRAM(CACHE_MEMORY_SEARCH, 1, 100, 100, LINEAR, "Time to search memory cache (ms)")
 HISTOGRAM(CACHE_DISK_SEARCH, 1, 100, 100, LINEAR, "Time to search disk cache (ms)")
 HISTOGRAM(CACHE_OFFLINE_SEARCH, 1, 100, 100, LINEAR, "Time to search offline cache (ms)")
@@ -274,16 +275,17 @@ CACHE_LOCK_HISTOGRAM(NSCACHEENTRYDESCRIP
 CACHE_LOCK_HISTOGRAM(NSCACHEENTRYDESCRIPTOR_GETFETCHCOUNT)
 CACHE_LOCK_HISTOGRAM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID)
 CACHE_LOCK_HISTOGRAM(NSCACHESERVICE_PROCESSREQUEST)
 CACHE_LOCK_HISTOGRAM(NSCACHESERVICE_VISITENTRIES)
 CACHE_LOCK_HISTOGRAM(NSCACHEENTRYDESCRIPTOR_GETPREDICTEDDATASIZE)
 CACHE_LOCK_HISTOGRAM(NSCACHEENTRYDESCRIPTOR_GETLASTFETCHED)
 CACHE_LOCK_HISTOGRAM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID)
 CACHE_LOCK_HISTOGRAM(NSBLOCKONCACHETHREADEVENT_RUN)
+CACHE_LOCK_HISTOGRAM(NSDISKCACHEMAP_REVALIDATION)
 
 #undef CACHE_LOCK_HISTOGRAM
  
 // DNS_LOOKUP_METHOD was renamed to DNS_LOOKUP_METHOD2 as the old version did not use enumerated values
 HISTOGRAM_ENUMERATED_VALUES(DNS_LOOKUP_METHOD2, 16, "DNS Lookup Type (hit, renewal, negative-hit, literal, overflow, network-first, network-shared)")
 HISTOGRAM(DNS_CLEANUP_AGE, 1, 1440, 50, EXPONENTIAL, "DNS Cache Entry Age at Removal Time (minutes)")
 HISTOGRAM(DNS_LOOKUP_TIME, 1, 60000, 50, EXPONENTIAL, "Time for a successful DNS OS resolution (msec)")
 HISTOGRAM(DNS_RENEWAL_TIME, 1, 60000, 50, EXPONENTIAL, "Time for a renewed DNS OS resolution (msec)")
@@ -327,16 +329,17 @@ HISTOGRAM(MOZ_STORAGE_ASYNC_REQUESTS_MS,
 HISTOGRAM_BOOLEAN(MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, "mozStorage async requests success")
 HISTOGRAM_ENUMERATED_VALUES(STARTUP_MEASUREMENT_ERRORS, mozilla::StartupTimeline::MAX_EVENT_ID, "Flags errors in startup calculation()")
 HISTOGRAM(NETWORK_DISK_CACHE_OPEN, 1, 10000, 10, EXPONENTIAL, "Time spent opening disk cache (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_TRASHRENAME, 1, 10000, 10, EXPONENTIAL, "Time spent renaming bad Cache to Cache.Trash (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_DELETEDIR, 1, 10000, 10, EXPONENTIAL, "Time spent deleting disk cache (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN, 1, 10000, 10, EXPONENTIAL, "Time spent during showdown stopping thread deleting old disk cache (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_SHUTDOWN, 1, 10000, 10, EXPONENTIAL, "Total Time spent (ms) during disk cache showdown")
 HISTOGRAM(NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE, 1, 10000, 10, EXPONENTIAL, "Time spent (ms) during showdown deleting disk cache for 'clear private data' option")
+HISTOGRAM(NETWORK_DISK_CACHE_REVALIDATION, 1, 10000, 10, EXPONENTIAL, "Total Time spent (ms) during disk cache revalidation")
 HISTOGRAM(NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE, 1, 10000, 10, EXPONENTIAL, "Time spent in nsDiskCacheOutputStream::Close() on non-main thread (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD, 1, 10000, 10, EXPONENTIAL, "Time spent in nsDiskCacheOutputStream::Close() on the main thread (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL, 1, 10000, 10, EXPONENTIAL, "Time spent in nsDiskCacheOutputStream::CloseInternal() on non-main thread (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD, 1, 10000, 10, EXPONENTIAL, "Time spent in nsDiskCacheOutputStream::CloseInternal on the main thread (ms)")
 
 /**
  * Idle service telemetry
  */